Here is what I have got till now. Thanks to Brian Rodgers :
public class JsonSerializeTest
{
[Fact]
public void deserialize_test()
{
var settings = new JsonSerializerSettings { ContractResolver = new CustomContractResolver() };
var jsonString = "{\"PropertyA\":\"Test\",\"PropertyB\":null}";
var jsonObject = JsonConvert.DeserializeObject<NoConfigModel>(jsonString, settings);
Assert.NotNull(jsonObject);
}
}
public class NoConfigModel
{
public string PropertyA { get; set; }
public int PropertyB { get; set; }
public bool PropertyC { get; set; }
}
class CustomContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
property.ShouldDeserialize = instance =>
{
try
{
PropertyInfo prop = (PropertyInfo)member;
if (prop.CanRead)
{
var value = prop.GetValue(instance, null);// getting default value(0) here instead of null for PropertyB
return value != null;
}
}
catch
{
}
return false;
};
return property;
}
}
My Problem:
Need to set default value to Not Nullable fields instead of Exception or whole object being null. Having missing value is not a problem (gives default value by DefaultContractResolver
), but when a not nullable value is explicitly set as null in json then this gives exception.
My code above is close but not close enough. I think I need to find a way to know that the value is actually null from json and set ShouldDeserialize =false
for those cases.
What you want is that, during deserialization, when a null
value is encountered for a non-nullable member, to set a default (non-null) value back in the containing object. This can be done by overriding DefaultContractResolver.CreateProperty
as follows:
class CustomContractResolver : DefaultContractResolver
{
class NullToDefaultValueProvider : ValueProviderDecorator
{
readonly object defaultValue;
public NullToDefaultValueProvider(IValueProvider baseProvider, object defaultValue) : base(baseProvider)
{
this.defaultValue = defaultValue;
}
public override void SetValue(object target, object value)
{
base.SetValue(target, value ?? defaultValue);
}
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (property != null && property.PropertyType.IsValueType && Nullable.GetUnderlyingType(property.PropertyType) == null && property.Writable)
{
var defaultValue = property.DefaultValue ?? Activator.CreateInstance(property.PropertyType);
// When a null value is encountered in the JSON we want to set a default value in the class.
property.PropertyType = typeof(Nullable<>).MakeGenericType(new[] { property.PropertyType });
property.ValueProvider = new NullToDefaultValueProvider(property.ValueProvider, defaultValue);
// Remember that the underlying property is actually not nullable so GetValue() will never return null.
// Thus the below just overrides JsonSerializerSettings.NullValueHandling to force the value to be set
// (to the default) even when null is encountered.
property.NullValueHandling = NullValueHandling.Include;
}
return property;
}
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static CustomContractResolver instance;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static CustomContractResolver() { instance = new CustomContractResolver(); }
public static CustomContractResolver Instance { get { return instance; } }
}
public abstract class ValueProviderDecorator : IValueProvider
{
readonly IValueProvider baseProvider;
public ValueProviderDecorator(IValueProvider baseProvider)
{
if (baseProvider == null)
throw new ArgumentNullException();
this.baseProvider = baseProvider;
}
public virtual object GetValue(object target) { return baseProvider.GetValue(target); }
public virtual void SetValue(object target, object value) { baseProvider.SetValue(target, value); }
}
Notes:
The contract resolver works by changing the type of properties returning non-nullable values to the corresponding Nullable<T>
type, then creating a ValueProvider
decorator that maps an incoming null
value to the former default value (which cannot be null, since the underlying type is not nullable).
It is not necessary to override JsonProperty.ShouldDeserialize
. This predicate allows a JSON property value to be dynamically ignored based on the state of the target object . It is not even passed the deserialized JSON value.
If your type uses a parameterized constructor you may also need to override DefaultContractResolver.CreatePropertyFromConstructorParameter
.
You may want to cache the contract resolver for best performance.
Working sample .Net fiddle here .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.