JSON.net不应该使用构造函数参数的默认值,应该使用默认属性

时间:2016-05-24 14:14:49

标签: c# .net json.net

有没有办法告诉JSON.net当它尝试使用构造函数反序列化时(如果没有默认构造函数),它不应该为构造函数参数赋值默认值,并且它应该只调用一个构造函数构造函数参数在JSON字符串中表示?同一个序列化程序在调用属性/字段设置器时应该使用默认值,规则只限于构造函数。这里的枚举值似乎都不合适:http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_DefaultValueHandling.htm

解决方案不应依赖于对要反序列化的类型应用任何属性。

例如,通过将Dog的age设置为0(int的默认值),json字符串"{}"将反序列化为类型Dog的对象。我想要一个通用的,不基于属性的解决方案来防止这种情况发生。在这种情况下,{"age":4}可以正常工作,因为在JSON字符串中指定了age并且对应于构造函数参数。

public class Dog
{
    public Dog(int age)
    {
        this.Age = age;
    }

    public int Age { get; }
}

但是,如果指定Dog,则"{}" 反序列化为年龄== 0的狗,因为不使用构造函数创建Dog

public class Dog
{   
    public int Age { get; set; }
}

并且要避免任何关于“你为什么要这样做”的问题......带有构造函数的对象通常在质量上与POCO不同,因为它与它们的属性有关。使用构造函数在POCO上存储属性值而不是可设置属性通常意味着您要验证/约束属性值。所以在存在constuctor的情况下不允许使用默认值进行反序列化是合理的。

1 个答案:

答案 0 :(得分:5)

当Json.NET遇到没有无参数构造函数但带有参数化构造函数的对象时,它将通过不区分大小写的最佳匹配算法调用该构造函数来创建对象matching the JSON property names to the constructor arguments by name using reflection。即名称也出现在构造函数中的属性将通过构造函数调用设置,而不是set方法(即使有一个)。

因此,您可以通过将等效属性标记为required来标记构造函数参数:

public class Dog
{
    public Dog(int age)
    {
        this.Age = age;
    }

    [JsonProperty(Required = Required.Always)]
    public int Age { get; }
}

现在JsonConvert.DeserializeObject<Dog>(jsonString)将在"age"属性丢失时抛出。

由于这是总是想要的东西,您可以创建一个继承自custom contract resolverDefaultContractResolverCamelCasePropertyNamesContractResolver,它会根据需要自动标记传递给构造函数的属性,不需要属性:

public class ConstructorParametersRequiredContractResolver : DefaultContractResolver
{
    // 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 ConstructorParametersRequiredContractResolver instance;

    static ConstructorParametersRequiredContractResolver() { instance = new ConstructorParametersRequiredContractResolver(); }

    public static ConstructorParametersRequiredContractResolver Instance { get { return instance; } }

    protected override JsonProperty CreatePropertyFromConstructorParameter(JsonProperty matchingMemberProperty, ParameterInfo parameterInfo)
    {
        var property = base.CreatePropertyFromConstructorParameter(matchingMemberProperty, parameterInfo);

        if (property != null && matchingMemberProperty != null)
        {
            var required = matchingMemberProperty.Required;
            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;
    }
}

然后

var settings = new JsonSerializerSettings { ContractResolver = ConstructorParametersRequiredContractResolver.Instance };
JsonConvert.DeserializeObject<T>(jsonString, settings)

将再次投掷。

(请注意,这仅适用于 相应属性的情况。似乎并不是一种直接的方法来标记构造函数参数,而不需要相应的属性。)