简体   繁体   中英

Using a custom ContractResolver, how to set a default value instead of null when deserializing a null JSON property to a value-type member?

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:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM