Default Value for Missing Properties with JSON.Net

Default value for missing properties with JSON.net

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

JSON.net, C#, unable to set default values in data model

You are using the wrong setting.

If you want null values in the JSON to be ignored during deserialization, you need to set NullValueHandling = NullValueHandling.Ignore in the settings. The default is Include, which means that if the JSON has an explicit null in it for a particular property, that property will be set to null on your object, overwriting any default you may have set.

DefaultValueHandling.Populate means if the key is missing altogether in the JSON (which is different from being set to null), then it will use the value provided by the [DefaultValue] attribute. Note that if the JSON has an explicit null in it, this setting will not use the default value from the attribute, even if NullValueHandling is set to Ignore. So if you want a default value in that situation, the best way to do that is by setting it in the constructor.

So, in short, to get the behavior you want:

  • Use the NullValueHandling.Ignore setting.
  • Provide your default values via the constructor.
  • You don't need the DefaultValueHandling setting or the [DefaultValue] attribute.

Demo fiddle: https://dotnetfiddle.net/kyUjFz

Specifying default value for a property in relation to other property in Json.NET

As specified in the JSON standard, a JSON object is an unordered set of name/value pairs, so in general Json.NET does not allow one property to be set relative to another. Json.NET is a streaming, single-pass deserializer and there's no guarantee which will appear first in the JSON.

However, when your object specifies use of a parameterized constructor via, e.g., the [JsonConstructor] attribute, Json.NET will pre-load the JSON properties and their values then construct the object with the deserialized values. This affords an opportunity to set the End property relative to the Start property:

public partial class JsonsoftExample
{
public JsonsoftExample() { }

[JsonConstructor]
JsonsoftExample(DateTime start, DateTime? end)
{
this.Start = start;
this.End = end ?? Start.AddHours(1);
}

[JsonProperty(Required = Required.Always)]
public DateTime Start { get; set; }

public DateTime End { get; set; }
}

Notes:

  • The names of the constructor arguments must be the same as the names of the properties, modulo case. This is how Json.NET matches constructor arguments with JSON properties.

  • Note that the End property is of type DateTime while the end constructor argument is of type DateTime?. When "End" is missing from the JSON, Json.NET will pass in a null value into the constructor, which can be checked for to properly initialize the End time relative to the Start.

  • If you don't want to serialize the End property when it is exactly one hour later than the start, you can use conditional property serialization:

    public bool ShouldSerializeEnd()
    {
    return End != Start.AddHours(1);
    }
  • The constructor does not need to be public when marked with [JsonConstructor]. Properties not passed into the constructor will be populated after construction as per usual deserialization.

Sample fiddle.

Json.net doesn't populate properties with default values when defined in the constructor

I was able to reproduce the change from Json 6.0.8 to 9.0.1. It appears that JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters() now checks for the presence of the [DefaultValue(DefaultText)] and [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] attributes on the constructor parameter rather than the corresponding property. This check is made around line 1979:

if (HasFlag(constructorProperty.DefaultValueHandling.GetValueOrDefault(Serializer._defaultValueHandling), DefaultValueHandling.Populate))
{
context.Value = EnsureType(
reader,
constructorProperty.GetResolvedDefaultValue(),
CultureInfo.InvariantCulture,
constructorProperty.PropertyContract,
constructorProperty.PropertyType);
}

Thus you can deserialize your type successfully by modifying the constructor as follows:

class MyTestClass
{
public const string DefaultText = "...";

[DefaultValue(DefaultText)]
[JsonProperty(PropertyName = "myText", DefaultValueHandling = DefaultValueHandling.Populate)]
public readonly string Text;

public MyTestClass([JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate), DefaultValue(DefaultText)] string text = DefaultText)
{
Text = text;
}
}

I'm not sure if this is an intentional or unintentional change. You might want to report an issue to ask. Or perhaps you already have?

Update

As another workaround, you could give your type a private parameterless constructor that sets the default value correctly, and mark it with [JsonConstructor]:

class MyTestClass
{
public const string DefaultText = "...";

[DefaultValue(DefaultText)]
[JsonProperty(PropertyName = "myText", DefaultValueHandling = DefaultValueHandling.Populate)]
public readonly string Text;

[JsonConstructor]
MyTestClass()
: this(DefaultText)
{
}

public MyTestClass(string text = DefaultText)
{
Text = text;
}
}

Json.NET will now call the private parameterless constructor, then correctly set the value for the readonly field Text through reflection, since it has been marked with [JsonProperty]. (Or even make the parameterless constructor public and make the parameter to the second constructor non-optional.)

Why when I deserialize with JSON.NET ignores my default value?

The DefaultValue attribute does not set the value of the property. See this question: .NET DefaultValue attribute

What you might be better off doing is setting the value in the constructor:

public class AssignmentContentItem
{
[JsonProperty("Id")]
public string Id { get; set; }
[JsonProperty("Qty")]
public int Quantity { get; set; }

public AssignmentContentItem()
{
this.Quantity = 1;
}
}

Where this line:

AssignmentContentItem item =
JsonConvert.DeserializeObject<AssignmentContentItem>("{\"Id\":\"Q0\"}");

Results in an AssignmentContentItem with its Quantity set to 1.

Ignoring some default values for serialization

You can use a variation of the DefaultValueContractResolver from this answer to Json.NET: How to make DefaultValueHandling only apply to certain types? to exclude all enum-valued properties with default values:

public class EnumDefaultValueContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.DefaultValueHandling == null)
{
if (property.PropertyType.IsEnum)
{
//For safety you could check here if the default value is named ValueNotSet and only set IgnoreAndPopulate in that case.
//var defaultValue = Enum.ToObject(property.PropertyType, 0);
//if (defaultValue.ToString() == "ValueNotSet")
//{
property.DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate; // Or DefaultValueHandling.Ignore if you prefer
//}
}
}

return property;
}
}

Then use it as follows:

var resolver = new EnumDefaultValueContractResolver();

var settings = new JsonSerializerSettings { ContractResolver = resolver };
var json = JsonConvert.SerializeObject(inventory, settings);

You may want to cache the contract resolver for best performance.

Demo fiddle here.



Related Topics



Leave a reply



Submit