首页 > 解决方案 > 当 JsonConstructor 参数名称与 JSON 不匹配时如何抛出异常?

问题描述

我正在反序列化一堆 C# 只读结构(它们的构造函数用 标记[JsonConstructor]),如果我收到的任何 JSON 格式不正确,我会尝试尽早失败。

不幸的是,如果构造函数参数和输入 JSON 之间存在命名差异,则该参数只会被分配一个默认值。有没有办法让我得到一个异常,所以这些默认值不会意外地“污染”我的其余业务逻辑?我试过玩各种JsonSerializerSettings但无济于事。

简化示例:

public readonly struct Foo {

    [JsonConstructor]
    public Foo(long wrong) {
        FooField = wrong;
    }

    public readonly long FooField;

}

public void JsonConstructorParameterTest() {

    // The Foo constructor parameter name ("wrong") doesn't match the JSON property name ("FooField").
    var foo = JsonConvert.DeserializeObject<Foo>("{\"FooField\":42}");

    // The foo.FooField is now 0.
    // How can we cause the above to throw an exception instead of just assigning 0 to Foo.FooField?

}

以上可以通过重命名来修复wronginto fooField,但我想知道在 0 之前已经提交到我的数据库。

标签: c#validationjson.net

解决方案


从这个答案到JSON.net的合同解析器不应该对构造函数参数使用默认值,应该对属性使用默认值几乎可以满足您的要求,但是它有一个明显的限制:

这仅在存在相应属性时才有效。似乎没有一种直接的方法可以根据需要标记没有相应属性的构造函数参数。

由于根据需要标记“不匹配”的构造函数参数似乎不起作用(此处为演示小提琴#1),如果找到不匹配的构造函数参数,您可以从该答案修改合同解析器,以便在合同构建期间抛出异常。

以下合约解析器执行此操作:

public class ConstructorParametersRequiredContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreatePropertyFromConstructorParameter(JsonProperty matchingMemberProperty, ParameterInfo parameterInfo)
    {
        // All constructor parameters are required to have some matching member.
        if (matchingMemberProperty == null)
            throw new JsonSerializationException(string.Format("No matching member for constructor parameter \"{0}\" of type \"{1}\".", parameterInfo, parameterInfo.Member.DeclaringType));
    
        var property = base.CreatePropertyFromConstructorParameter(matchingMemberProperty, parameterInfo);

        if (property != null && matchingMemberProperty != null)
        {
            if (!matchingMemberProperty.IsRequiredSpecified) // If the member is already explicitly marked with some Required attribute, don't override it.
            {
                Required required;
            
                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;
    }
}

要使用它,请构建解析器:

static IContractResolver resolver = new ConstructorParametersRequiredContractResolver();

和单元测试如下:

var settings = new JsonSerializerSettings
{
    ContractResolver = resolver,
};
JsonConvert.DeserializeObject<Foo>("{\"FooField\":42}", settings);

请注意,您可能希望缓存和重用合约解析器以获得最佳性能

演示小提琴#2在这里


推荐阅读