使自定义JsonConverter在与JSON.NET序列化时尊重ItemTypeNameHandling

本文关键字:ItemTypeNameHandling 序列化 NET 自定义 JsonConverter 在与 JSON | 更新日期: 2023-09-27 17:50:52

我有一个包含基类子对象列表的对象。子对象需要自定义转换器。我不能使我的自定义转换器尊重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属性?

使自定义JsonConverter在与JSON.NET序列化时尊重ItemTypeNameHandling

深入研究Json的源代码。. Net(版本4.5 release 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方法。问题就在这里:因为它们是空的,ShouldWriteType方法的逻辑意味着它返回false,因此没有写入类型。

编辑:

受此启发,您可以通过自定义转换器的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中提取的。净源。

您当然需要修改WriteJson方法来输出其余的字段,但希望这能奏效。

相关文章:
  • 没有找到相关文章