89

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?

somdoron
  • 4,653
  • 2
  • 16
  • 24
  • 4
    In C# 6, this will be `public int Age { get; private set; } = 5;` if I'm not mistaken. – Millie Smith Apr 13 '15 at 17:34
  • 2
    possible duplicate of [Why when I deserialize with JSON.NET ignores my default value?](http://stackoverflow.com/questions/15452450/why-when-i-deserialize-with-json-net-ignores-my-default-value) – Sherif Ahmed Apr 13 '15 at 17:35
  • Possible duplicate of http://stackoverflow.com/questions/15452450/why-when-i-deserialize-with-json-net-ignores-my-default-value – Millie Smith Apr 13 '15 at 17:35
  • I'm using constructor, so it will not work. – somdoron Apr 13 '15 at 17:37

5 Answers5

150

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

See Json.Net Reference

Teng-pao Yu
  • 1,313
  • 15
  • 30
somdoron
  • 4,653
  • 2
  • 16
  • 24
  • 17
    Another option is IgnoreAndPopulate, which will skip serialization of members whose value matches the default value, which makes your final serialized string smaller. On deserialization, it works just like Populate, setting the default value if it's not present in the JSON string. – Triynko Feb 08 '16 at 23:59
15

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. 
}
Tono Nam
  • 34,064
  • 78
  • 298
  • 470
  • This works only in types where the deserializer calls a constructor that does not set the property. Here, it calls the default constructor. – Palec Sep 13 '22 at 14:17
3

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; }
Karthikeyan VK
  • 5,310
  • 3
  • 37
  • 50
1

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

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);
    }
}
Vinz
  • 3,030
  • 4
  • 31
  • 52