【问题标题】:Custom Json Serializer for deep nested objects用于深度嵌套对象的自定义 Json 序列化器
【发布时间】:2017-12-14 00:24:30
【问题描述】:

我有几个生成的 .NET 类深入三层,我想以特殊格式对它们进行序列化。因此,我开始使用 Newtonsoft.Json 编写自定义 Json 序列化器。

我相信很难完全解释,所以我把代码和目标一起贴在这里:https://dotnetfiddle.net/CDGcMW

本质上,有一个包含对象的初始数组,并且会有该对象的属性。困难的部分是这些属性是未知的,这就是我尝试创建自定义序列化程序的原因。

任何帮助我确定如何使这里生成的 Json https://dotnetfiddle.net/CDGcMW 成为被注释掉的“目标”JSON。

编辑:将 dotnetfiddle 更新为更小的示例。原文在这里:https://dotnetfiddle.net/dprfDu

【问题讨论】:

  • 不太确定我是否理解这个问题。您是否希望在深度嵌套在对象图中而不是在图顶部时排除类型的属性?如果是这样,也许Json.NET serialize by depth and attribute 会有所帮助。
  • 如果该链接没有帮助,有什么方法可以将您的小提琴简化为minimal reproducible example,以尽可能少的无关字段和属性显示当前输出和所需输出?跨度>
  • 谢谢,我已将示例更新为更短,我将研究 IContractResolver。
  • 抱歉,忘记保存了。
  • 当您的类型的一个实例(直接或间接)嵌套在您的类型的另一个实例中时,声明您想要使用不同的转换逻辑是否正确且足够?

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


【解决方案1】:

您的“目标”JSON 处理起来很棘手,因为 SubDataMappers 列表的处理方式会有所不同,具体取决于子级是具有非空 DataMapperProperty 还是非空 SubDataMappers 列表。在前一种情况下,您希望它呈现为一个对象,每个子对象都包含一个属性DataMapper;在后者中,作为一个对象数组,每个对象包含一个DataMapper。另外,我看到您使用 DataMapperName 属性作为 JSON 中的键,而不是作为众所周知的属性的值。考虑到这两个限制,我认为最好的攻击计划是创建一个在DataMappers列表 上运行的JsonConverter,而不是单个实例。否则,转换器代码会变得非常混乱。如果这是可以接受的,那么下面的转换器应该会给你你想要的:

public class DataMapperListConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(List<DataMapper>);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        List<DataMapper> list = (List<DataMapper>)value;
        if (list.Any(dm => dm.DataMapperProperty != null))
        {
            JObject obj = new JObject(list.Select(dm =>
            {
                JToken val;
                if (dm.DataMapperProperty != null)
                    val = JToken.FromObject(dm.DataMapperProperty, serializer);
                else 
                    val = JToken.FromObject(dm.SubDataMappers, serializer);
                return new JProperty(dm.Name, val);
            }));
            obj.WriteTo(writer);
        }
        else
        {
            serializer.Serialize(writer,
                list.Select(dm => new Dictionary<string, List<DataMapper>>
                {
                    { dm.Name, dm.SubDataMappers }
                }));
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Object)
        {
            return token.Children<JProperty>()
                 .Select(jp => 
                 {
                     DataMapper mapper = new DataMapper { Name = jp.Name };
                     JToken val = jp.Value;
                     if (val["data-type"] != null)
                         mapper.DataMapperProperty = jp.Value.ToObject<DataMapperProperty>(serializer);
                     else
                         mapper.SubDataMappers = jp.Value.ToObject<List<DataMapper>>(serializer);
                     return mapper;
                 })
                 .ToList();
        }
        else if (token.Type == JTokenType.Array)
        {
            return token.Children<JObject>()
                .SelectMany(jo => jo.Properties())
                .Select(jp => new DataMapper
                {
                    Name = jp.Name,
                    SubDataMappers = jp.Value.ToObject<List<DataMapper>>(serializer)
                })
                .ToList();
        }
        else
        {
            throw new JsonException("Unexpected token type: " + token.Type.ToString());
        }
    }
}

假设:

  • 您永远不会自己序列化单个DataMapper;它将始终包含在一个列表中。
  • DataMappers 可以嵌套到任意深度。
  • DataMapper 将始终有一个非空的 Name,它在每个级别都是唯一的。
  • DataMapper 永远不会同时具有非空 DataMapperProperty 和非空列表 SubDataMappers
  • DataMapperProperty 将始终有一个非空的 DataType
  • DataMapper 永远不会有 Namedata-type

如果最后四个假设不成立,那么这种 JSON 格式将不适用于您正在尝试做的事情,您需要重新考虑。

要使用转换器,您需要将其添加到您的序列化程序设置中,如下所示。在序列化和反序列化时都使用这些设置。从DataMapper 类中删除[JsonConverter] 属性。

var settings = new JsonSerializerSettings()
{
    Converters = new List<JsonConverter> { new DataMapperListConverter() },
    Formatting = Formatting.Indented
};

这是一个往返演示:https://dotnetfiddle.net/8KycXB

【讨论】:

  • 非常感谢您的帮助。我很感激!经过进一步的开发,有一个场景是有一个对象有三层深,这在我的原始示例中是没有的。我分叉了您的代码并对其进行了调整,以包含一个三层深的对象,因为这导致该对象为空。这是代码:dotnetfiddle.net/AFl36o。再次感谢你,我学到了很多!
  • 这是我拥有的 WriteJson 函数,它比您的解决方案要混乱得多,而且我没有让 ReadJson 工作。 dotnetfiddle.net/fAKOrK。使您的解决方案支持三个深度的任何帮助都将令人难以置信!
  • 我看到了第 3 级出现空值的原因。您现在在第二级有一个异构的 SubDataMapper 列表:其中两个项目具有 DataMapperProperties,而新的项目具有一个 SubMapper 列表。我的转换器期望所有一个或所有另一个,因为它依赖于它作为确定是将列表呈现为数组还是对象的一种手段。我可以修复转换器以遵循序列化格式,但在反序列化时,这会导致一个新问题:(续)
  • ... 不再有办法判断 JSON 对象是表示具有 DataMapperProperty 的 DataMapper 还是具有 SubMapper 列表的 DataMapper。级别 3 将呈现为对象而不是数组,因为它包含具有 DataMapperProperty 的子项。所以,我们需要另一种方式来区分。让我问你这个问题——如果 DataMapper 有一个 DataMapperProperty,我能否相信它总是有一个非空的 data-type,并且没有一个 DataMapper 会有一个名为 @987654351 的 Name @?如果是这样,我想我可以让它工作。
  • 有趣,谢谢你的解释!是的,DataMapperProperty 将始终包含非空数据类型(以及源。不会出现名称也称为数据类型的情况。我真诚地感谢您的帮助!
【解决方案2】:

您可以通过将所有类型化的类替换为 ExpandoObject 来使用 JSON.NET 实现深度嵌套序列化。这个对我有用。让我知道它是否适合您或需要任何样品向您展示。

更新:

这是工作示例

https://dotnetfiddle.net/jtebDs

希望这是您希望看到的输出。如果您有任何问题,请告诉我。

【讨论】:

  • 这将与@brian-rogers 解决方案或其他不同的解决方案一起使用吗?我可以通过创建这个(虽然很杂乱)代码来让 WriteJson 函数工作:dotnetfiddle.net/fAKOrK。事实证明,制作 ReadJson 函数很困难。如果您能指出我的示例/示例,我将不胜感激。
猜你喜欢
  • 1970-01-01
  • 2022-01-04
  • 2014-03-14
  • 1970-01-01
  • 1970-01-01
  • 2019-04-26
  • 1970-01-01
  • 2011-01-23
  • 1970-01-01
相关资源
最近更新 更多