首页 > 解决方案 > Newtonsoft.JSON 的 ContractResolver 与自定义转换器冲突

问题描述

我有这个示例分层数据:

{
  "Name": "Car 1",
  "AvailableColors": [
    "Red",
    "Green"
  ],
  "RelatedItems": {
    "Brand": {
      "Name": "Brand 1",
      "RelatedItems": {
        "ImportingCompanies": [
          {
            "Name": "Company 1",
            "RelatedItems": {
              "CeoName": "CEO 1"
            }
          },
          {
            "Name": "Company 2",
            "RelatedItems": {
              "CeoName": "CEO 2"
            }
          }
        ]
      }
    }
  }
}

基本上我需要的是将任何属性的所有属性复制RelatedItems到其父对象。结果应该变成:

{
  "Name": "Car 1",
  "AvailableColors": [
    "Red",
    "Green"
  ],
  "Brand": {
    "Name": "Brand 1",
    "ImportingCompanies": [
      {
        "Name": "Company 1",
        "CeoName": "CEO 1"
      },
      {
        "Name": "Company 2",
        "CeoName": "CEO 2"
      }
    ]
  }
}

我正在使用 ASP.NET Core,并以这种方式配置了我的启动:

.AddNewtonsoftJson(options =>
{
    options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    options.SerializerSettings.Converters.Add(new RelatedItemsFlattenerJsonConverter());
});

这是我写的代码RelatedItemsFlattenerJsonConverter

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    JToken token = JToken.FromObject(value);
    if (token.Type == JTokenType.Array)
    {
        var array = (JArray)token;
        foreach (var item in token)
        {
            FlattenRelatedItems(item);
        }
    }
    else if (token.Type == JTokenType.Object)
    {
        var @object = (JObject)token;
        FlattenRelatedItems(@object);
    }
    token.WriteTo(writer);
    stopwatch.Stop();
}

private void FlattenRelatedItems(JToken token)
{
    if (token.Type == JTokenType.Array)
    {
        var array = (JArray)token;
        foreach (var item in token)
        {
            FlattenRelatedItems(item);
        }
    }
    else if (token.Type == JTokenType.Object)
    {
        var @object = (JObject)token;
        var relatedItemsProperty = @object.Properties().FirstOrDefault(i => i.Name.ToLower() == "RelatedItems".ToLower());
        if (relatedItemsProperty.IsNotNull())
        {
            var relatedItems = @object[relatedItemsProperty.Name];
            @object.Remove(relatedItemsProperty.Name);
            var keys = ((JObject)relatedItems).Properties().Select(i => i.Name).ToList();
            foreach (var key in keys)
            {
                @object.Add(key, relatedItems[key]);
            }
        }
        var properties = @object.Properties().ToList();
        foreach (var property in properties)
        {
            FlattenRelatedItems(@object[property.Name]);
        }
    }
}

它就像一个魅力。但是,当我添加 custom 时RelatedItemsFlattenerJsonConverter,我的 API 响应的大小写将变为PascalCase d。当我不添加它时,它ContractResolver受到尊重,我的 API 响应是camelCase d。我应该怎么做才能同时拥有自定义转换器和API 的camelCase ?

标签: c#asp.net-corejson.net

解决方案


当您调用JToken.FromObject(value)insideWriteJson时,您没有将序列化程序传递给它,因此它不知道CamelCasePropertyNamesContractResolver您已配置的(或任何其他设置)。 JToken.FromObject()默认情况下在内部使用新的序列化程序实例。

但是,在这种情况下,如果按原样传入序列化程序,则可能会遇到自引用循环,因为转换器会尝试调用自身。您需要做的是创建自己的JsonSerializerinside实例,WriteJson然后从传递给WriteJson. 然后将该新的序列化程序传递给JToken.FromObject().

换句话说,改变这一行:

JToken token = JToken.FromObject(value);

对此:

var innerSerializer = new JsonSerializer();
innerSerializer.ContractResolver = serializer.ContractResolver;
// if any other settings from the outer serializer are needed, copy them here
JToken token = JToken.FromObject(value, innerSerializer);

推荐阅读