使用自定义ContractResolver,当将空JSON属性反序列化为值类型成员时,如何设置默认值而不是null?

时间:2018-09-11 13:16:49

标签: c# json.net json-deserialization

这是我到现在为止所拥有的。感谢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;
    }
}

我的问题:

需要将默认值设置为“不可为空”字段,而不是“异常”或整个对象为空。缺少值不是问题(通过DefaultContractResolver提供默认值),但是当在json中将不可为空的值显式设置为null时,这会产生异常。

我上面的代码很接近,但是不够接近。我想我需要找到一种方法来知道json中的值实际上为空,并为这些情况设置ShouldDeserialize =false

1 个答案:

答案 0 :(得分:2)

您想要的是,在反序列化期间,当为不可为空的成员遇到null值时,请在包含的值中将默认(非空)值设置为宾语。这可以通过如下重写DefaultContractResolver.CreateProperty来完成:

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

注意:

工作示例.Net小提琴here