How to throw an exception when JsonConstructor parameter name doesn't match JSON?

落爺英雄遲暮 提交于 2021-02-10 07:09:09

问题


I'm deserializing a bunch of C# readonly structures (which have their constructors marked by [JsonConstructor]), and I'm trying to fail early if any JSON that I receive is malformed.

Unfortunately, if there is a naming discrepancy between the constructor parameter and the input JSON, the parameter just gets assigned a default value. Is there a way that I could get an exception instead, so these defaults don't accidentally "pollute" the rest of my business logic? I have tried playing with various JsonSerializerSettings but to no avail.

Simplified example:

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?

}

The above can be fixed by renaming wrong into fooField, but I'd like to know that before 0 has already been committed to my database.


回答1:


The contract resolver from this answer to JSON.net should not use default values for constructor parameters, should use default for properties almost does what you want, however it has a noted restriction:

This only works if there is a corresponding property. There doesn't appear to be a straightforward way to mark a constructor parameter with no corresponding property as required.

Since marking an "unmatched" constructor parameter as required doesn't seem to work (demo fiddle #1 here) you can modify the contract resolver from that answer to throw an exception during contract construction if an unmatched constructor parameter is found.

The following contract resolver does this:

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

To use it, construct the resolver:

static IContractResolver resolver = new ConstructorParametersRequiredContractResolver();

And unit test as follows:

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

Note you may want to cache and reuse the contract resolver for best performance.

Demo fiddle #2 here.



来源:https://stackoverflow.com/questions/63898984/how-to-throw-an-exception-when-jsonconstructor-parameter-name-doesnt-match-json

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!