C# 使用JSON.NET序列化时,使自定义JsonConverter尊重ItemTypeNameHandling

C# 使用JSON.NET序列化时,使自定义JsonConverter尊重ItemTypeNameHandling,c#,.net,json,serialization,json.net,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 () {

我有一个带有基类子对象列表的对象。子对象需要自定义转换器。我无法使自定义转换器尊重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);
        }
    }
}
必要的输出如果我注释ItemConverterType=typeofToyConverter,则会发生这种情况,行:

我尝试临时更改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)
这里重要的是containerContract和containerProperty参数。在没有转换器的情况下进行序列化时,将提供这些参数,并且ShouldWriteType能够使用它们来确定要使用的TypeNameHandling

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

SerializeValue(jsonWriter, value, GetContractSafe(value), null, null, null);
请注意,最后两个参数实际上是JsonContainerContract和JsonProperty containerProperty,并沿链传递给ShouldWriteType方法。这就是问题所在:因为它们是null,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.Net中剥离出来的


当然,您需要修改WriteJson方法以输出其余字段,但希望这能起到作用。

深入研究了Json.Net 4.5版11版的源代码后,您想要做的似乎是不可能的

将类型写入输出的关键在于此方法:

Newtonsoft.Json.Serialization.JsonSerializerInternalWriter
    .ShouldWriteType(TypeNameHandling typeNameHandlingFlag, JsonContract contract,
        JsonProperty member, JsonContainerContract containerContract,
        JsonProperty containerProperty)
这里重要的是containerContract和containerProperty参数。在没有转换器的情况下进行序列化时,将提供这些参数,并且ShouldWriteType能够使用它们来确定要使用的TypeNameHandling

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

SerializeValue(jsonWriter, value, GetContractSafe(value), null, null, null);
请注意,最后两个参数实际上是JsonContainerContract和JsonProperty containerProperty,并沿链传递给ShouldWriteType方法。这就是问题所在:因为它们是null,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.Net中剥离出来的


当然,您需要修改WriteJson方法以输出其余字段,但希望这样做。

您的目标是.NET的哪个版本?我要说的是,您不需要类型转换器来实现玩具子类型的序列化/反序列化。你也可以试着仔细阅读和理解答案。我将删除我的答案。似乎您已经知道了一切,不需要任何帮助。@I4V我需要一个自定义转换器用于其他目的,与$type无关。您的目标是.NET的哪个版本?我要说的是,您不需要类型转换器,以实现玩具子类型的序列化/反序列化。你也可以试着仔细阅读和理解答案。我将删除我的答案。似乎您已经知道了一切,不需要任何帮助。@I4V我需要一个自定义转换器用于其他目的,与$type无关。您能建议一些解决方法吗?或者我应该将其报告为错误/功能请求您的解决方案建议使用低级API从头开始重写整个序列化。很明显,我不想手工做任何事情。好吧,我想剩下的唯一选择就是对作业对象做一些技巧。。。我会尽量和图书馆的作者联系。没错,但这就是使用转换器的意义所在;。出于兴趣,你需要用一个吗?我觉得你实际承担的任务比你的问题要复杂得多。如果你想发布更多的细节,我很乐意进一步研究。我已经定制了阅读,但我不需要定制写作。一
d这就是原因:请参见答案中的JsonRefedConverter类。在CodePlex上发布了一个问题:你能建议一些解决方法吗?或者我应该将其报告为错误/功能请求您的解决方案建议使用低级API从头开始重写整个序列化。很明显,我不想手工做任何事情。好吧,我想剩下的唯一选择就是对作业对象做一些技巧。。。我会尽量和图书馆的作者联系。没错,但这就是使用转换器的意义所在;。出于兴趣,你需要用一个吗?我觉得你实际承担的任务比你的问题要复杂得多。如果你想发布更多的细节,我很乐意进一步研究。我已经定制了阅读,但我不需要定制写作。这就是原因:请参见答案中的JsonRefedConverter类。在CodePlex上发布了一个问题: