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:
- No attributes should be required
- No fields should be serialized/deserialized only properties
Sounds easy. Lets dig in.
Update
The source NuGet package has been replaced by a new package that contains more resolvers and are distributed in binary from instead of source.
- NuGet: https://www.nuget.org/packages/JsonNet.ContractResolvers/
- Repo: https://github.com/danielwertheim/jsonnet-contractresolvers
- Old source package: This has been replaced with the package above. Old post about source package..
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