定制Json.NET序列化:将对象转换为数组,以避免重复属性名称 [英] Customizing Json.NET serialization: turning object into array to avoid repetition of property names

查看:117
本文介绍了定制Json.NET序列化:将对象转换为数组,以避免重复属性名称的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在从服务器向客户端发送大量不同的JSON图(我控制两者),并且它们都包含病理情况:大量同类(相同类型)值.因此,例如,部分有效负载如下:

I'm sending large amounts of different JSON graphs from a server to a client (I control both) and they all contain a pathological case: a large array of homogeneous (same type) values. So, for example, part of the payload looks like:

[{"LongPropertyName":87, "AnotherVeryLongPropertyName":93,
  "BlahBlahBlahBlahBlah": 78},
 {"LongPropertyName":97, "AnotherVeryLongPropertyName":43,
  "BlahBlahBlahBlahBlah": 578},
 {"LongPropertyName":92, "AnotherVeryLongPropertyName":-3,
  "BlahBlahBlahBlahBlah": 817}, ...

我添加了一些格式,但是正如您所看到的那样,从霍夫曼编码的角度来看,这很可笑,即应该有效地表达普通事物.

I've added some formatting, but as you can see, it's ridiculous from a Huffman coding point of view, ie that common things should be efficiently expressed.

因此,由于我同时控制反序列化和序列化的结束,因此我想在以下位置实现转换:

So, since I control both the deserialization and the serialization ends, I want to implement a transform where this:

[{"Key1":87,"Key2":99},{"Key1":42,"Key2":-8}]

变成这样的东西:

[["$","Key1","Key2"],[87,99],[42,-8]]

如您所见,即使只有两个对象,它也更紧凑.

which as you can see is more compact even with just two objects.

我在哪里连接到Json.NET进行此转换?我想对尽可能多的对象自动执行此操作.我找到了 ContractResolvers ,但是我不确定它们是否确实在我想要的阶段发生-我不确定如何使用其方法将JSON对象/字典转换为数组.

Where do I hook into Json.NET to do this transformation? I want to do this automatically for as many objects as possible. I've found ContractResolvers but I'm not sure if they're happening at the stage I want - I'm not sure how to use its methods to turn a JSON object/dictionary into an array.

或者,如果已经为Json.NET实现了类似的操作,则我想改用它.但是我对要进行的更改种类(见上文)并不感到困惑,只是我将其连接到Json.NET来实现它.

Alternatively, if a similar thing has already been implemented for Json.NET, I'd want to use that instead. But I am not confused about the sort of change I want to make (see above), just where I'd hook into Json.NET to make it happen.

((我尝试过将其压缩.它可以正常工作,并且可以减少70%到95%的距离,但是它仍然必须输出完整的JSON文本并进行所有压缩/解压缩.这个问题是:我如何输出从一开始就提供更紧凑的数据形式?)

(I have tried gzipping it. It works fine and shaves off between 70% and 95%, but it still has to output the full JSON text and do all that compression/decompression. This question is: how do I just output a more compact form of the data from the beginning?)

更新:JsonConverter是您执行此操作的方法.我已经写了几本,但是出于某种原因,我认为它们会发生冲突.

Update: The way you do this is with a JsonConverter. I had already written several but for some reason I thought they would conflict.

我最后得到的是Brian Rogers的基础,并进行了一些更改,以嵌入/展平任何直接包含的对象.这不是原始问题的一部分,但是我这样做的原因是因为如果我有:

What I ended up with was Brian Rogers' base along with some changes to also embed/flatten any directly contained objects. This was not part of the original question, but the reason I did that is because if I had:

[{"A": 42,"B":{"PropOne":87,"PropTwo":93,"PropThree":78}},
{"A":-72,"B":{"PropOne":97,"PropTwo":43,"PropThree":578}]

...我最终得到:

[["A","B"],[42,{"PropOne":87,"PropTwo":93,"PropThree":78}],
[-72,{"PropOne":97,"PropTwo":43,"PropThree":578}]]

...那并不能真正节省任何东西.而如果我将对象嵌入/展平作为其组成键,则最终会得到:

...and that doesn't really save anything. Whereas if I embedded/flattened the object as its constituent keys, I end up with:

[["A","B_PropOne","B_PropTwo","B_PropThree"],[42,87,93,78],[-72,97,43,578]]

推荐答案

我相信实现您正在寻找的内容的最佳方法是使用@Ilija Dimov建议的自定义JsonConverter.他的转换器是一个很好的开始,并且在某些情况下应该可以正常工作,但是如果要序列化更复杂的对象图,则可能会遇到麻烦.我提供以下转换器作为替代解决方案.该转换器具有以下优点:

I believe the best way to achieve what you are looking for is to use a custom JsonConverter as was suggested by @Ilija Dimov. His converter is a good start, and should work fine for certain cases, but you may run into trouble if you are serializing a more complex graph of objects. I offer the following converter as an alternative solution. This converter has the following advantages:

  • 对列表项使用Json.Net的内置序列化逻辑,以便遵守应用于类的任何属性,包括[JsonConstructor][JsonProperty].其他转换器也受到尊重.
  • 忽略基元和字符串列表,以便它们可以正常序列化.
  • 支持List<YourClass>,其中YourClass包含复杂对象,包括List<YourOtherClass>.
  • Uses the Json.Net's built-in serialization logic for the list items, so that any attributes applied to the classes are respected, including [JsonConstructor] and [JsonProperty]. Other converters are respected as well.
  • Ignores lists of primitives and strings so that these are serialized normally.
  • Supports List<YourClass> where YourClass contains complex objects, including List<YourOtherClass>.

限制:

  • 当前不支持任何可枚举的列表,例如List<List<YourClass>>List<Dictionary<K, YourClass>>,但可以根据需要进行修改.这些将立即以通常的方式进行序列化.
  • Does not currently support lists of anything enumerable, e.g. List<List<YourClass>> or List<Dictionary<K, YourClass>>, but could be modified to do so if needed. These will be serialized in the usual way for now.

这是转换器的代码:

class ListCompactionConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        // We only want to convert lists of non-enumerable class types (including string)
        if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(List<>))
        {
            Type itemType = objectType.GetGenericArguments().Single();
            if (itemType.IsClass && !typeof(IEnumerable).IsAssignableFrom(itemType))
            {
                return true;
            }
        }
        return false;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JArray array = new JArray();
        IList list = (IList)value;
        if (list.Count > 0)
        {
            JArray keys = new JArray();

            JObject first = JObject.FromObject(list[0], serializer);
            foreach (JProperty prop in first.Properties())
            {
                keys.Add(new JValue(prop.Name));
            }
            array.Add(keys);

            foreach (object item in list)
            {
                JObject obj = JObject.FromObject(item, serializer);
                JArray itemValues = new JArray();
                foreach (JProperty prop in obj.Properties())
                {
                    itemValues.Add(prop.Value);
                }
                array.Add(itemValues);
            }
        }
        array.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        IList list = (IList)Activator.CreateInstance(objectType);  // List<T>
        JArray array = JArray.Load(reader);
        if (array.Count > 0)
        {
            Type itemType = objectType.GetGenericArguments().Single();

            JArray keys = (JArray)array[0];
            foreach (JArray itemValues in array.Children<JArray>().Skip(1))
            {
                JObject item = new JObject();
                for (int i = 0; i < keys.Count; i++)
                {
                    item.Add(new JProperty(keys[i].ToString(), itemValues[i]));
                }

                list.Add(item.ToObject(itemType, serializer));
            }
        }
        return list;
    }
}

下面是使用此转换器的完整往返演示.我们有一个可变的Company对象列表,每个对象都包含一个不可变的Employees列表.出于演示目的,每个公司还使用自定义JSON属性名称提供了一个简单的字符串别名列表,并且我们还使用IsoDateTimeConverter为员工HireDate自定义日期格式.转换器通过JsonSerializerSettings类传递给序列化器.

Below is a full round-trip demo using this converter. We have a list of mutable Company objects which each contain a list of immutable Employees. For demonstration purposes, each company also has a simple list of string aliases using a custom JSON property name, and we also use an IsoDateTimeConverter to customize the date format for the employee HireDate. The converters are passed to the serializer via the JsonSerializerSettings class.

class Program
{
    static void Main(string[] args)
    {
        List<Company> companies = new List<Company>
        {
            new Company
            {
                Name = "Initrode Global",
                Aliases = new List<string> { "Initech" },
                Employees = new List<Employee>
                {
                    new Employee(22, "Bill Lumbergh", new DateTime(2005, 3, 25)),
                    new Employee(87, "Peter Gibbons", new DateTime(2011, 6, 3)),
                    new Employee(91, "Michael Bolton", new DateTime(2012, 10, 18)),
                }
            },
            new Company
            {
                Name = "Contoso Corporation",
                Aliases = new List<string> { "Contoso Bank", "Contoso Pharmaceuticals" },
                Employees = new List<Employee>
                {
                    new Employee(23, "John Doe", new DateTime(2007, 8, 22)),
                    new Employee(61, "Joe Schmoe", new DateTime(2009, 9, 12)),
                }
            }
        };

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.Converters.Add(new ListCompactionConverter());
        settings.Converters.Add(new IsoDateTimeConverter { DateTimeFormat = "dd-MMM-yyyy" });
        settings.Formatting = Formatting.Indented;

        string json = JsonConvert.SerializeObject(companies, settings);
        Console.WriteLine(json);
        Console.WriteLine();

        List<Company> list = JsonConvert.DeserializeObject<List<Company>>(json, settings);
        foreach (Company c in list)
        {
            Console.WriteLine("Company: " + c.Name);
            Console.WriteLine("Aliases: " + string.Join(", ", c.Aliases));
            Console.WriteLine("Employees: ");
            foreach (Employee emp in c.Employees)
            {
                Console.WriteLine("  Id: " + emp.Id);
                Console.WriteLine("  Name: " + emp.Name);
                Console.WriteLine("  HireDate: " + emp.HireDate.ToShortDateString());
                Console.WriteLine();
            }
            Console.WriteLine();
        }
    }
}

class Company
{
    public string Name { get; set; }
    [JsonProperty("Doing Business As")]
    public List<string> Aliases { get; set; }
    public List<Employee> Employees { get; set; }
}

class Employee
{
    [JsonConstructor]
    public Employee(int id, string name, DateTime hireDate)
    {
        Id = id;
        Name = name;
        HireDate = hireDate;
    }
    public int Id { get; private set; }
    public string Name { get; private set; }
    public DateTime HireDate { get; private set; }
}

这是上面演示的输出,显示了中间JSON以及从中反序列化的对象的内容.

Here is the output from the above demo, showing the intermediate JSON as well as the contents of the objects deserialized from it.

[
  [
    "Name",
    "Doing Business As",
    "Employees"
  ],
  [
    "Initrode Global",
    [
      "Initech"
    ],
    [
      [
        "Id",
        "Name",
        "HireDate"
      ],
      [
        22,
        "Bill Lumbergh",
        "25-Mar-2005"
      ],
      [
        87,
        "Peter Gibbons",
        "03-Jun-2011"
      ],
      [
        91,
        "Michael Bolton",
        "18-Oct-2012"
      ]
    ]
  ],
  [
    "Contoso Corporation",
    [
      "Contoso Bank",
      "Contoso Pharmaceuticals"
    ],
    [
      [
        "Id",
        "Name",
        "HireDate"
      ],
      [
        23,
        "John Doe",
        "22-Aug-2007"
      ],
      [
        61,
        "Joe Schmoe",
        "12-Sep-2009"
      ]
    ]
  ]
]

Company: Initrode Global
Aliases: Initech
Employees:
  Id: 22
  Name: Bill Lumbergh
  HireDate: 3/25/2005

  Id: 87
  Name: Peter Gibbons
  HireDate: 6/3/2011

  Id: 91
  Name: Michael Bolton
  HireDate: 10/18/2012


Company: Contoso Corporation
Aliases: Contoso Bank, Contoso Pharmaceuticals
Employees:
  Id: 23
  Name: John Doe
  HireDate: 8/22/2007

  Id: 61
  Name: Joe Schmoe
  HireDate: 9/12/2009

我在这里添加了小提琴,以防您想玩代码.

I've added a fiddle here in case you'd like to play with the code.

这篇关于定制Json.NET序列化:将对象转换为数组,以避免重复属性名称的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆