Serializing null in JSON.NET
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
Related videos on Youtube
Comments
-
AviD over 1 year
When serializing arbitrary data via JSON.NET, any property that is null is written to the JSON as
"propertyName" : null
This is correct, of course.
However I have a requirement to automatically translate all nulls into the default empty value, e.g. null
string
s should becomeString.Empty
, nullint?
s should become0
, nullbool?
s should befalse
, and so on.NullValueHandling
is not helpful, since I dont want toIgnore
nulls, but neither do I want toInclude
them (Hmm, new feature?).So I turned to implementing a custom
JsonConverter
.
While the implementation itself was a breeze, unfortunately this still didnt work -CanConvert()
is never called for a property that has a null value, and thereforeWriteJson()
is not called either. Apparently nulls are automatically serialized directly intonull
, without the custom pipeline.For example, here is a sample of a custom converter for null strings:
public class StringConverter : JsonConverter { public override bool CanConvert(Type objectType) { return typeof(string).IsAssignableFrom(objectType); } ... public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { string strValue = value as string; if (strValue == null) { writer.WriteValue(String.Empty); } else { writer.WriteValue(strValue); } } }
Stepping through this in the debugger, I noted that neither of these methods are called for properties that have a null value.
Delving into JSON.NET's sourcecode, I found that (apparently, I didnt go into a lot of depth) there is a special case checking for nulls, and explictly calling
.WriteNull()
.For what it's worth, I did try implementing a custom
JsonTextWriter
and overriding the default.WriteNull()
implementation...public class NullJsonWriter : JsonTextWriter { ... public override void WriteNull() { this.WriteValue(String.Empty); } }
However, this can't work well, since the
WriteNull()
method knows nothing about the underlying datatype. So sure, I can output""
for any null, but that doesnt work well for e.g. int, bool, etc.So, my question - short of converting the entire data structure manually, is there any solution or workaround for this?
-
Samuel Slade about 12 yearsI'm guessing the
WriteNull()
method is called internally within the JSON serialization process and you can't determine which value you are currently serializing? -
AviD about 12 yearsThe WriteNull method is called by the JsonSerializer when the property has a null value. To be accurate, the value I'm serializing is always null :), but yes there seems to be no way to know the underlying data type for which the null is being written.
-
J. Holmes about 12 yearsWhat's the point of using nullable types if you are just going to ignore null as a valid state of the object?
-
AviD about 12 years@32bitkid good question, but in this case the nullable types are used server side (e.g. in the model), but the client/receiving end/view does not handle the null state very well (yes, I would rather have the client fixed, but its a complicated situation). In any event, just because I start with nullable types, shouldnt mean it cant be serialized into something else...
-
ShelbyZ about 12 yearsIs it possible to override any of the WriteValue(Nullable<T>) for your case where you want the default value for that type? Is the corresponding method called such as for a int? is WriteValue(Nullable<int>) called or does it directly flow to WriteNull()? JsonTextWriter
-
svick about 12 yearsAfter cursory look at the source code of Json.Net, I don't think you can do this easily without modifying its source code.
-
J. Holmes about 12 years@ShelbyZ
if(value==null)
is pretty much the first case :( -
Brian Chavez almost 12 yearsGosh I wish there was a way to serialize NULL when a JsonConverterAttribute has been specified on a property. I specifically need to modify output something other than "null" on nullable properties in my case too. Perhaps we can get a feature request going for this.
-
Brian Chavez almost 12 years
-
-
Ian Jacobs about 12 yearsI believe you can do Type.IsValueType if you want.
-
J. Holmes about 12 years@IanJacobs I figured it out by using
GetGenericTypeDefinition() == typeof (Nullable<>)
. -
AviD about 12 yearsWow, this is... a bit more complicated than I expected. Especially for something so trivial... Anyway it will take me a while to plug this in and check, but it looks good! Thanks, in the meantime...
-
AviD about 12 yearsWill also need to put in a special case for String, since it's not actually a Nullable<> type...
-
J. Holmes about 12 years@AviD Whoops, I forgot that you also needed to translate strings. But it would be implemented more or less the same way. You could create a new ValueProvider just for strings, or change the existing signature rather than passing in the generic type, just pass in the default value (and move the
Activator.CreateInstance()
up one level) -
J. Holmes about 12 years@AviD and I agree, it wasn't as intuitive as I thought it would be, but that was the only solution I could come up with that fits within the existing architecture of JSON.NET and doesn't require any changes to the library itself.
-
AviD about 12 yearsYeah, from there its pretty simple to stretch to cover strings, I would just add another condition in there, pretty much in the same pattern. And hey, I'm not complaining about the complicated, just surprised... Thanks for this!
-
AviD about 12 yearsExcellent, this worked brilliantly! I did make a few small changes, such as pulling out the generic parameter inside the valueprovider, and setting the default only on demand, but overall your solution with IValueProvider did the trick! Thanks a ton!
-
Guillaume Beauvois almost 8 yearsI know we don't do this here but, after a neverending day : Thank you !