c# - 使用 JSON.NET 进行序列化时,使自定义 JsonConverter 尊重 ItemTypeNameHandling

标签 c# .net json serialization json.net

我有一个带有基类子对象列表的对象。子对象需要自定义转换器。我无法让我的自定义转换器尊重 ItemTypeNameHandling 选项。

示例代码(创建一个新的C#控制台项目,添加JSON.NET NuGet包):

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace My {
    class Program {
        private static void Main () {
            Console.WriteLine(JsonConvert.SerializeObject(
                new Box { toys = { new Spintop(), new Ball() } },
                Formatting.Indented));
            Console.ReadKey();
        }
    }

    [JsonObject] class Box
    {
        [JsonProperty (
            ItemConverterType = typeof(ToyConverter),
            ItemTypeNameHandling = TypeNameHandling.Auto)]
        public List<Toy> toys = new List<Toy>();
    }
    [JsonObject] class Toy {}
    [JsonObject] class Spintop : Toy {}
    [JsonObject] class Ball : Toy {}

    class ToyConverter : JsonConverter {
        public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer) {
            serializer.Serialize(writer, value);
        }
        public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
            return serializer.Deserialize(reader, objectType);
        }
        public override bool CanConvert (Type objectType) {
            return typeof(Toy).IsAssignableFrom(objectType);
        }
    }
}

产生的输出:

{
  "toys": [
    {},
    {}
  ]
}

必要的输出(如果我注释 ItemConverterType = typeof(ToyConverter), 行,就会发生这种情况):

{
  "toys": [
    {
      "$type": "My.Spintop, Serialization"
    },
    {
      "$type": "My.Ball, Serialization"
    }
  ]
}

我尝试临时更改 ToyConverter.WriteJson 方法中 serializer.TypeNameHandling 的值,但它会影响不相关的属性。 (当然,我真正的转换器比这更复杂。这只是一个具有基本功能的示例。)

问题:如何使我的自定义 JsonConverter 尊重 JsonProperty 属性的 ItemTypeNameHandling 属性?

最佳答案

深入研究 Json.Net(版本 4.5 版本 11)的源代码后,看起来您想要做的事情似乎是不可能的。

将类型写入输出的关键是这个方法:

Newtonsoft.Json.Serialization.JsonSerializerInternalWriter
    .ShouldWriteType(TypeNameHandling typeNameHandlingFlag, JsonContract contract,
        JsonProperty member, JsonContainerContract containerContract,
        JsonProperty containerProperty)

这里重要的是 containerContractcontainerProperty 参数。在没有转换器的情况下进行序列化时,会提供这些参数,并且 ShouldWriteType 能够使用它们来确定要使用的 TypeNameHandling

但是,当使用转换器进行序列化时,不会提供这两个参数。这似乎是因为 ToyConverter.WriteJson 导致对 Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue 的调用,如下所示:

SerializeValue(jsonWriter, value, GetContractSafe(value), null, null, null);

请注意,最后两个参数实际上是 JsonContainerContract containerContractJsonProperty containerProperty,并沿链向下传递到 ShouldWriteType 方法。问题就在这里:因为它们是 null,所以 ShouldWriteType 方法的逻辑意味着它返回 false,从而未写入类型。

编辑:

灵感来自this ,您可以通过自定义转换器的 WriteJson 方法来解决此问题:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    writer.WriteStartObject();
    writer.WritePropertyName("$type");
    writer.WriteValue(RemoveAssemblyDetails(value.GetType().AssemblyQualifiedName.ToString()));
    writer.WriteEndObject();
}

private static string RemoveAssemblyDetails(string fullyQualifiedTypeName)
{
    StringBuilder builder = new StringBuilder();

    // loop through the type name and filter out qualified assembly details from nested type names
    bool writingAssemblyName = false;
    bool skippingAssemblyDetails = false;
    for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
    {
        char current = fullyQualifiedTypeName[i];
        switch (current)
        {
            case '[':
                writingAssemblyName = false;
                skippingAssemblyDetails = false;
                builder.Append(current);
                break;
            case ']':
                writingAssemblyName = false;
                skippingAssemblyDetails = false;
                builder.Append(current);
                break;
            case ',':
                if (!writingAssemblyName)
                {
                    writingAssemblyName = true;
                    builder.Append(current);
                }
                else
                {
                    skippingAssemblyDetails = true;
                }
                break;
            default:
                if (!skippingAssemblyDetails)
                    builder.Append(current);
                break;
        }
    }

    return builder.ToString();
}

请注意,RemoveAssemblyDetails 方法是直接从 Json.Net source 中提取的。 .

您当然需要修改 WriteJson 方法来输出其余字段,但希望这能解决问题。

关于c# - 使用 JSON.NET 进行序列化时,使自定义 JsonConverter 尊重 ItemTypeNameHandling,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14182961/

相关文章:

javascript - 使用 JS 将数组转换为 JSON 对象

c# - 我真的锁定了这些东西吗

c# - 对多对象列表进行排序的正确方法?

javascript - 创建一个 Json Cookie 数组?

java - Spring:使用 POST 请求保存带有外键的对象

c# - 如何通过 ASP.NET 重定向和修改无扩展名的 URL?

c# - 析构函数、处理方法和终结方法之间的区别

c# - CSharpProvider 运行时编译找不到 DLL

c# - 如何在 VS 类设计器中创建自动实现的属性

c# - 取决于类型 T 的枚举