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);
}
}