Default value for missing properties with JSON.net
C#json.netC# Problem Overview
I'm using Json.net to serialize objects to database.
I added a new property to the class (which is missing in the json in the database) and I want the new property to have a default value when missing in the json.
I tried DefaultValue attribute but it's doesn't work. I'm using private setters and constructor to deserialize the json, so setting the value of the property in the constructor will not work as there is a parameter with the value.
Following is an example:
class Cat
{
public Cat(string name, int age)
{
Name = name;
Age = age;
}
public string Name { get; private set; }
[DefaultValue(5)]
public int Age { get; private set; }
}
static void Main(string[] args)
{
string json = "{\"name\":\"mmmm\"}";
Cat cat = JsonConvert.DeserializeObject<Cat>(json);
Console.WriteLine("{0} {1}", cat.Name, cat.Age);
}
I expect the age to be 5 but it is zero.
Any suggestions?
C# Solutions
Solution 1 - C#
I found the answer, just need to add the following attribute as well:
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
In your example:
class Cat
{
public Cat(string name, int age)
{
Name = name;
Age = age;
}
public string Name { get; private set; }
[DefaultValue(5)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public int Age { get; private set; }
}
static void Main(string[] args)
{
string json = "{\"name\":\"mmmm\"}";
Cat cat = JsonConvert.DeserializeObject<Cat>(json);
Console.WriteLine("{0} {1}", cat.Name, cat.Age);
}
Solution 2 - C#
You can also have a default value as:
class Cat
{
public string Name { get; set; }
public int Age { get; set; } = 1 ; // one is the default value. If json property does not exist when deserializing the value will be one.
}
Solution 3 - C#
Add [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
,Change your Age property from
[DefaultValue(5)]
public int Age { get; private set; }
to
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
[DefaultValue(5)]
public string Age { get; private set; }
Solution 4 - C#
I had same issue as someone already described where probably existing constructor made DefaultValueHandling not working. I played a bit around and found out that it can be bypassed with NullValueHandling.
[DefaultValue(5)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate, NullValueHandling = NullValueHandling.Ignore)]
public int Age { get; set; }
Solution 5 - C#
As the question's title is phrased more broadly (than just handling a single default value for one single missing JSON property), here's a more generic solution for handling missing JSON properties on a global level:
To handle all missing JSON properties in more complex models, a ContractResolver is likely needed. (other means are inconsistent with missing properties or null value handling in general)
The idea is to use DefaultContractResolver.CreateProperty() to "hook" into the creation of JSON properties and specify desired behavior, which will run on all members (also nested ones) relevant during the (de-)serialization process:
public class MyContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
// do smth with the property (settings) here
return prop;
}
}
Here is an example for how this can be used to default-initialize all collection types to empty collections instead of null, when they are missing (what initially brought me to this question):
public class EmptyCollectionJsonResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
// default initialize collections to empty instead of null
if (IsCollection(prop.PropertyType))
{
prop.DefaultValue = Activator.CreateInstance(prop.PropertyType); // creates empty collection
prop.DefaultValueHandling = DefaultValueHandling.Populate; // force population on null / missing parsed members
}
return prop;
}
// helper function to determine if a type is a collection
private static readonly Type TColl = typeof(ICollection<>);
private static bool IsCollection(Type t)
{
return t.IsGenericType
&& TColl.IsAssignableFrom(t.GetGenericTypeDefinition()) || t.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == TColl);
}
}