danielwertheim

danielwertheim


notes from a passionate developer

Developer that lives by the mantra "code is meant to be shared".

Share


Tags


Disclaimer

This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

Json.Net - private setters

Daniel WertheimDaniel Wertheim

This post is a simple on explaining how you can make Json.Net handle private setters. I will show you three solutions but only one fullfills my requirements:

Sounds easy. Lets dig in.

Update

There's now a source distribution NuGet for this. Read more in a separate blog post.

Alternative 1 - Using attributes

This solution fails on the requirement "No attributes should be required" but I will show it anyway. Using the JsonPropertyAttribute you can get private setters to be "supported" when deserializing.

[TestMethod]
public void When_it_is_decorated_with_attribute()  
{
    var json = @"{""Name"":""Daniel""}";

    var entity = JsonConvert.DeserializeObject<Person>(json);

    Assert.AreEqual("Daniel", entity.Name);
}

public class Person  
{
    [JsonProperty]
    public string Name { get; private set; }

    public void SetName(string name)
    {
        Name = name;
    }
}
Test Passed  

Alternative 2 - DefaultContractResolver.DefaultMembersSearchFlags

This solution fails on the requirement "No fields should be serialized/deserialized only properties" but I will show it anyway.

[TestMethod]
public void When_member_search_flags_include_non_public()  
{
    var contractResolver = new DefaultContractResolver();
    contractResolver.DefaultMembersSearchFlags |= BindingFlags.NonPublic;
    var settings = new JsonSerializerSettings
    {
        ContractResolver = contractResolver
    };
    var json = @"{""Name"":""Daniel""}";

    var entity = JsonConvert.DeserializeObject<Person>(json, settings);

    Assert.AreEqual("Daniel", entity.Name);
}

public class Person  
{
    public string Name { get; private set; }

    public void SetName(string name)
    {
        Name = name;
    }
}
Test Passed  

Alternative 3 - a custom contract resolver

This solution fulfils my requirements that I have in SisoDb of no attributes and only change the behaviour for private setters.

[TestMethod]
public void When_using_the_custom_contract_resolver()  
{
    var contractResolver = new SisoJsonDefaultContractResolver();
    var settings = new JsonSerializerSettings
    {
        ContractResolver = contractResolver
    };
    var json = @"{""Name"":""Daniel""}";

    var entity = JsonConvert.DeserializeObject<Person>(json, settings);

    Assert.AreEqual("Daniel", entity.Name);
}

public class Person  
{
    public string Name { get; private set; }

    public void SetName(string name)
    {
        Name = name;
    }
}

public class SisoJsonDefaultContractResolver  
    : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(
        MemberInfo member,
        MemberSerialization memberSerialization)
    {
        var prop = base.CreateProperty(member, memberSerialization);

        if (!prop.Writable)
        {
            var property = member as PropertyInfo;
            if (property != null)
            {
                var hasPrivateSetter = property.GetSetMethod(true) != null;
                prop.Writable = hasPrivateSetter;
            }
        }

        return prop;
    }
}
Test Passed  

What it does is to extend the DefaultContractResolver and if the created JsonProperty isn't already marked as writeable and the member is a property and not a field, we check if there is a private setter for use.

That's it. Of course I wouldn't consume it like that every time, instead I would put it in an application specific abstraction that uses JSON.Net as a component.

//Daniel

Developer that lives by the mantra "code is meant to be shared".

Comments