Is there a way to tell JSON.net that when it attempts to deserialize using a constructor (if there is no default constructor), that it should NOT assign default value to constructor parameters and that it should only call a constructor if every constructor parameter is represented in the JSON string? This same serializer SHOULD use default values when calling property/field setters, the rule is only scoped to constructors. None of the enum values here seem to be appropriate: http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_DefaultValueHandling.htm
The solution should NOT rely on applying any attributes to the types being deserialized.
for example, the json string "{}"
will deserialize to an object of type Dog
by setting the Dog's age to 0 (the default value for an int). I'd like to a generalized, not-attribute-based solution to prevent this from happening. In this case, {"age":4}
would work because age
is specified in the JSON string and corresponds to the constructor parameter.
public class Dog
{
public Dog(int age)
{
this.Age = age;
}
public int Age { get; }
}
However, if Dog
is specified as such, then "{}"
should deserialize to a Dog with Age == 0, because the Dog is not being created using a constructor.
public class Dog
{
public int Age { get; set; }
}
As to "why would you want to do this"... Objects with constructors are typically qualitatively different than POCOs as it relates to their properties. Using a constructor to store property values instead of settable properties on a POCO typically means that you want to validate/constrain the property values. So it's reasonable not to allow deserialization with default values in the presence of constructor(s).
When Json.NET encounters an object without a parameterless constructor but with a parameterized constructor, it will call that constructor to create the object, matching the JSON property names to the constructor arguments by name using reflection via a case-insensitive best match algorithm. I.e. a property whose name also appears in the constructor will be set via the constructor call, not the set method (even if there is one).
Thus, you can mark a constructor argument as required by marking the equivalent property as required:
public class Dog
{
public Dog(int age)
{
this.Age = age;
}
[JsonProperty(Required = Required.Always)]
public int Age { get; }
}
Now JsonConvert.DeserializeObject<Dog>(jsonString)
will throw when the "age"
property is missing.
Since this is something you always want, you can create a custom contract resolver inheriting from DefaultContractResolver
or CamelCasePropertyNamesContractResolver
that marks properties passed to the constructor as required automatically, without the need for attributes:
public class ConstructorParametersRequiredContractResolver : DefaultContractResolver
{
protected override JsonProperty CreatePropertyFromConstructorParameter(JsonProperty matchingMemberProperty, ParameterInfo parameterInfo)
{
var property = base.CreatePropertyFromConstructorParameter(matchingMemberProperty, parameterInfo);
if (property != null && matchingMemberProperty != null)
{
var required = matchingMemberProperty.Required;
// If the member is already explicitly marked with some Required attribute, don't override it.
// In Json.NET 12.0.2 and later you can use matchingMemberProperty.IsRequiredSpecified to check to see if Required is explicitly specified.
// if (!matchingMemberProperty.IsRequiredSpecified)
if (required == Required.Default)
{
if (matchingMemberProperty.PropertyType != null && (matchingMemberProperty.PropertyType.IsValueType && Nullable.GetUnderlyingType(matchingMemberProperty.PropertyType) == null))
{
required = Required.Always;
}
else
{
required = Required.AllowNull;
}
// It turns out to be necessary to mark the original matchingMemberProperty as required.
property.Required = matchingMemberProperty.Required = required;
}
}
return property;
}
}
Then construct an instance of the resolver:
static IContractResolver requiredResolver = new ConstructorParametersRequiredContractResolver();
And use it as follows:
var settings = new JsonSerializerSettings { ContractResolver = requiredResolver };
JsonConvert.DeserializeObject<T>(jsonString, settings)
Now deserialization will throw if the "age"
property is missing from the JSON.
Notes:
This only works if there is a corresponding property. There doesn't appear to be a straightforward way to mark a constructor parameter with no corresponding property as required.
Newtonsoft recommends that you cache and reuse the contract resolver for best performance.
Demo fiddle here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With