When serializing arbitrary data via JSON.NET, any property that is null is written to the JSON as
\"propertyName\" : null
This
Okay, I think I've come up with a solution (my first solution wasn't right at all, but then again I was on the train). You need to create a special contract resolver and a custom ValueProvider for Nullable types. Consider this:
public class NullableValueProvider : IValueProvider
{
private readonly object _defaultValue;
private readonly IValueProvider _underlyingValueProvider;
public NullableValueProvider(MemberInfo memberInfo, Type underlyingType)
{
_underlyingValueProvider = new DynamicValueProvider(memberInfo);
_defaultValue = Activator.CreateInstance(underlyingType);
}
public void SetValue(object target, object value)
{
_underlyingValueProvider.SetValue(target, value);
}
public object GetValue(object target)
{
return _underlyingValueProvider.GetValue(target) ?? _defaultValue;
}
}
public class SpecialContractResolver : DefaultContractResolver
{
protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
{
if(member.MemberType == MemberTypes.Property)
{
var pi = (PropertyInfo) member;
if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof (Nullable<>))
{
return new NullableValueProvider(member, pi.PropertyType.GetGenericArguments().First());
}
}
else if(member.MemberType == MemberTypes.Field)
{
var fi = (FieldInfo) member;
if(fi.FieldType.IsGenericType && fi.FieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
return new NullableValueProvider(member, fi.FieldType.GetGenericArguments().First());
}
return base.CreateMemberValueProvider(member);
}
}
Then I tested it using:
class Foo
{
public int? Int { get; set; }
public bool? Boolean { get; set; }
public int? IntField;
}
And the following case:
[TestFixture]
public class Tests
{
[Test]
public void Test()
{
var foo = new Foo();
var settings = new JsonSerializerSettings { ContractResolver = new SpecialContractResolver() };
Assert.AreEqual(
JsonConvert.SerializeObject(foo, Formatting.None, settings),
"{\"IntField\":0,\"Int\":0,\"Boolean\":false}");
}
}
Hopefully this helps a bit...
Edit – Better identification of the a Nullable<>
type
Edit – Added support for fields as well as properties, also piggy-backing on top of the normal DynamicValueProvider
to do most of the work, with updated test