在默认设置中更改 ContractResolver 后自定义 JsonConverter 失败

Custom JsonConverter is failing after changing the ContractResolver in the default settings

我为 NewtonSoft 制作了一个自定义的 JsonConvert JSON...经过全面测试,现在可以正常工作一段时间了。但是有人说属性需要驼峰式而不是 PascalCased。

所以,好的...我更改了默认设置中的 NamingStrategy。

JsonConvert.DefaultSettings = () =>  new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    DateTimeZoneHandling = DateTimeZoneHandling.Utc,
    Converters = new List<JsonConverter>
    {
        new StringEnumConverter(),
        new MessageEnvelopJsonConverter(PayloadTypes.GetMappings())
    },
    ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new CamelCaseNamingStrategy()
    }
};

果然,输出工作正常......这并不奇怪;转换器仅使用读取来确保接口的正确实现属性,但没有写入实现。

现在获取我的版本信息失败,GetValue 现在不再找到 属性。

private int GetMessageVersion(JObject jObject)
{
    JToken versionToken = jObject.GetValue(nameof(MessageEnvelop.Version));

    if (versionToken is null)
    {
        throw new MandatoryPropertyMissingException(nameof(MessageEnvelop.Version));
    }

    return versionToken.Value<int>();
}

我使用 JObject.Load 和来自转换器的 reader 来创建使用的 JObject,所以我假设这将确保这个命名约定的翻译......错误的假设。

如何让我的转换器使用正确的命名策略,从而更有弹性?

JObject.GetValue(String) is case sensitive, so if you just want to mimic the serializer's ordinal case insensitivity, you can call JObject.GetValue(String, StringComparison):

JToken versionToken = jObject.GetValue(nameof(MessageEnvelop.Version), StringComparison.OrdinalIgnoreCase);

这应该足以处理由于骆驼大小写引起的命名差异,但如果您想处理更一般的命名更改,您可以使用 Json.NET 自己的 contract information 来确定 JSON 属性 来自基础 .NET 属性 名称的名称,使用以下扩展方法:

public static partial class JsonExtensions
{
    public static T GetValueByUnderlyingName<T>(this JObject jObject, IContractResolver resolver, Type type, string underlyingName)
    {
        if (!resolver.TryGetJsonPropertyNameByUnderlyingName(type, underlyingName, out var jsonName))
            throw new ArgumentException(underlyingName);
        
        JToken versionToken = jObject.GetValue(jsonName);

        if (versionToken is null)
            throw new MandatoryPropertyMissingException(underlyingName);

        return versionToken.Value<T>();
    }
    
    public static bool TryGetJsonPropertyByUnderlyingName(this IContractResolver resolver, Type type, string underlyingName, out JsonProperty property) =>
        TryGetJsonPropertyByUnderlyingName(resolver, type, underlyingName, false, out property);

    public static bool TryGetJsonPropertyByUnderlyingName(this IContractResolver resolver, Type type, string underlyingName, bool exact, out JsonProperty property)
    {
        resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
        
        var contract = resolver.ResolveContract(type) as JsonObjectContract;
        if (contract == null)
            throw new ArgumentException();
        
        property = null;
        foreach (var p in contract.Properties)
        {
            if (p.UnderlyingName == underlyingName)
            {
                property = p;
                return true;
            }
            if (property == null && string.Equals(p.UnderlyingName, underlyingName, StringComparison.OrdinalIgnoreCase))
            {
                property = p;
            }
        }
        return property != null;
    }
    
    public static bool TryGetJsonPropertyNameByUnderlyingName(this IContractResolver resolver, Type type, string underlyingName, out string jsonName) =>
        TryGetJsonPropertyNameByUnderlyingName(resolver, type, underlyingName, false, out jsonName);
    
    public static bool TryGetJsonPropertyNameByUnderlyingName(this IContractResolver resolver, Type type, string underlyingName, bool exact, out string jsonName)
    {
        if (resolver.TryGetJsonPropertyByUnderlyingName(type, underlyingName, exact, out var p))
        {
            jsonName = p.PropertyName;
            return true;
        }
        jsonName = null;
        return false;
    }
}

要使用它,请在 JsonConverter<T>.ReadJson() 中为 resolver 参数传入 serializer.ContractResolver,并为 type 参数传入 objectType,如下所示:

var version = jObject.GetValueByUnderlyingName<int>(serializer.ContractResolver, objectType, nameof(MessageEnvelop.Version));

演示 fiddle here.