【问题标题】:Making custom JsonConverter respect ItemTypeNameHandling when serializing with JSON.NET使用 JSON.NET 进行序列化时,使自定义 JsonConverter 尊重 ItemTypeNameHandling
【发布时间】:2013-01-06 14:15:25
【问题描述】:

我有一个带有基类子对象列表的对象。子对象需要自定义转换器。我无法让我的自定义转换器尊重 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 尊重 ItemTypeNameHandling 属性的 JsonProperty 属性?

【问题讨论】:

  • 您的目标是什么版本的 .NET?
  • 我说的是你不需要TypeConvertor,来实现Toy子类型的序列化/反序列化。您也可以尝试仔细阅读和理解答案。我会删除我的答案。看来你已经什么都知道了,不需要任何帮助。
  • @I4V 我需要一个用于其他目的的自定义转换器,与“$type”无关。

标签: c# .net json serialization json.net


【解决方案1】:

深入研究了 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 方法以输出其余字段,但希望这样可以解决问题。

【讨论】:

  • 您能提出任何解决方法吗?或者也许我应该将其报告为错误/功能请求? :)
  • 您的解决方案建议使用低级 API 从头开始​​重写整个序列化。显然,我不想手动完成所有操作。好吧,我想剩下的唯一选择是用 JObjects 做一些技巧......我会尝试与库作者取得联系。
  • 确实如此,但这就是使用转换器的意义所在;)。出于兴趣,您需要使用一个吗?我觉得您实际执行的任务比您的问题要复杂得多。如果您想发布更多详细信息,我很乐意对此进行更多调查。
  • 我已经自定义了阅读,但是我不需要自定义写作。这就是为什么:Serializing heavily linked data in .NET (customizing JSON.NET references)(请参阅答案中的JsonRefedConverter 类)。在 CodePlex 上发布问题:Custom JsonConverter does not respect ItemTypeNameHandling option
猜你喜欢
  • 1970-01-01
  • 2016-06-05
  • 2016-03-28
  • 1970-01-01
  • 1970-01-01
  • 2017-03-19
  • 1970-01-01
  • 2016-05-18
  • 1970-01-01
相关资源
最近更新 更多