C#/Python Json序列化时控制字段顺序

小柊 发表于 2018年08月13日 22时39分49秒

一、背景

JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

 

Json广泛的用于数据传输的时候(比如Web的前后端数据传输),对于这种场景我们不会刻意的去控制对象序列化后,Json字符串中的字段顺序。因为在这种场景下,我们的要求只是能用就好。但一些特殊的情况,例如需要对一个对象进行Json序列化,并对产生的Json字符串进行RSA签名,那就一定要控制Json字符串中的字段顺序了。因为如果不严格的控制字段顺序,明明是一样的对象会因为序列化后Json字符串中的字段顺序不一致,导致产生的签名结果不一致。

那么,在C#和Python里,有什么方法可以控制对象字段在Json序列化时的顺序呢?

 

注:

后面的实现中,为了保证展示的效果,将使用以下测试数据:

用户(User)字段:

字段名 字段类型(C#/Python)
Uid int/int 100000
Username string/str hiiragi
Password string/str 0123456789abcdef0123456789abcdef
Phone string/str 13412341234
Enable bool/bool true

 

要求输出的Json字符串中,所有的字段以字段名升序排序。

 

二、Python 下的实现

这次我们先讲一下Python下的实现,因为比较简单。

Python下,我们常常会把需要序列化的数据存到一个字典(dict)对象中,但在Python中,字典对象是利用散列表实现的,这就导致了默认的字典对象是无序的。也就是说,您存入字典的键值对顺序可能会因为Python自动扩容字典时的重新哈希而被破坏。

例如下面的代码:

 

上面的代码笔者在运行完后输出字典对象d,解释器的输出结果是这样的:

 

很明显,这个顺序不是我当时存入时使用的顺序。

 

为了解决这个问题,Python提供了一个叫做OrderedDict的字典变种,它能够在添加键时保持顺序,所以在每一次对键进行迭代的时候都能保证顺序是一定的。

现在,我们就以第一章中的测试数据为例,进行一个简单的演示:

 

运行以上的代码,可以获得最后的结果:

 

可以,正是符合我们想要的结果。由此可以得出结论:在Python中,如果需要控制Json序列化后Json字符串中的字段顺序,可以使用Python提供的OrderedDict,并将想要的字段顺序依次放入字典中后,再序列化即可。

顺带一提,如果在Python中,需要对Json反序列化后产生的字典也能根据Json字符串中字段的顺序排列,可以通过设置json.loads函数的“object_hook”参数为OrderedDict即可,这样反序列化的结果便是一个OrderedDict,且其中的字段顺序与Json字符串中的字段顺序一致:

 

 

三、C# 下的实现

相比于Python,要控制Json序列化后产生的Json字符串中字段的顺序,C#就比较麻烦了。

一个是因为C#没有自带的Json序列化库(emmmmm,咱们就不要提System.Web.Script.Serialization. JavaScriptSerializer了好么,它不仅要手动添加框架引用System.Web.Extenisions,而且存在感极低,连亲爹微软都不怎么用它),二是C#里一般不用字典去存待序列化的数据,而是新建一个模型类的。

在.net平台下,现在最常用的Json序列化包一定就是Newtonsoft.Json里,所以接下来我们就以Newtonsoft.Json为例进行讲解:

先创建一个User模型类:

 

我们先看看不做任何处理时,利用Newtonsoft.Json序列化后的结果:

 

输出结果:

 

C#要控制Newtonsoft.Json对指定类型序列化时的字段顺序,有以下两种方法:

 

1.利用 JsonProperty 属性

先讲一个非常简单但拓展性不是特别好的方法吧,那就是JsonProperty属性:

首先引入命名空间Newtonsoft.Json:

 

好了,现在就可以在模型类中的字段上添加属性“JsonProperty”了。我们可以按F12查看从元数据,我们可以看到JsonPropertyAttribute类有一个公开属性“Order”,这个属性的摘要是:

Gets or sets the order of serialization of a member.

获取或设置一个成员的序列化顺序。

 

所以我们为各个属性添加JsonProperty属性,并根据情况设置其Order属性的值,比如本文之前提到的情景,可以修改User模型类为:

 

完成后重新运行序列化代码,输出序列化后的Json字符串:

 

完美达到效果。

 

2.自定义 ContractResolver

刚刚提到的利用JsonProperty属性来手动调整Newtonsoft.Json对一个模型类中字段序列化顺序的方法,有一个非常明显的不足,那就是后期对这个模型类拓展时,每增加一个新的字段都需要重新计算各个字段的Order值,拓展性非常的差。

那还有没有什么方法呢?答案当然是有的,那就是我们自定义一个ContractResolver,自定义的ContractResolver可以在序列化时被Newtonsoft.Json所调用,并通过CreateProperties方法的输出来进一步的控制Newtonsoft.Json序列化对象时字段输出的顺序。

不过从零开始重写一个ContractResolver实在还是有点麻烦,所以我们可以偷把懒,以Newtonsoft.Json提供的DefaultContractResolver为基类,重写它的CreateProperties方法~:

引入三个命名空间:System.Linq(用于排序)、Newtonsoft.Json(CreateProperties方法中的第二个参数类型所在的命名空间)和Newtonsoft.Json.Serialization(DefaultContractResolver类所在的命名空间)

 

重写的CreateProperties方法中的代码被缩写成一句话了,如果要展开来,可以展开成下面两行:

 

第一行负责调用父类(也就是DefaultContractResolver类)的CreateProperties方法,并将取得返回值;然后再利用Linq的拓展方法,根据列表中各元素(JsonProperty对象)的属性名称进行排序,最后重新整理为列表并返回。

 

完成OrderedContractResolver的编写后,并不能直接看到效果,因为这个OrderedContractResolver需要在序列化对象时作为参数传给SerializeObject方法,所以我们序列化对象的代码就变成了:

 

运行查看效果:

 

完美达成效果。

 

四、小结

本篇文章主要讨论了在C#和Python中,如果控制对象在Json序列化时各个字段的输出顺序。

Python相对简单,只需要使用OrderDict按顺序填入字段名和字段值即可。

C#则以Newtonsoft.Json为例,可以使用JsonProperty属性和自定义ContractResolver类来控制Json序列化时字段的顺序。通过设置JsonProperty属性来调整Json字段顺序的方式虽然简单,但拓展性并不高,适合于属性相对较少的模型;而自定义ContractResolver类的方式可以灵活的控制对象在序列化时各个字段的输出,例如调整顺序,删除字段等,但相对于JsonProperty属性的方式而言,比较复杂度。

 

五、参考文档:

1.《Order of serialized fields using JSON.NET

 

 

 

小柊

2018年8月13日 22:02:52

 

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注