添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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

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

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

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

用户(User)字段:

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

二、Python 下的实现

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

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

例如下面的代码:

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

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

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

"Username": "hiiragi", "Password": "0123456789abcdef0123456789abcdef", "Phone": "13412341234", "Enable": True # 对现有的 user 字典的所有键进行排序,并根据排序后的顺序重新构建 OrderedDict user = OrderedDict([ (key, user[key]) for key in sorted(user) json = dumps(user) print(json)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from json import dumps
from collections import OrderedDict
user = {
"Uid" : 100000 ,
"Username" : "hiiragi" ,
"Password" : "0123456789abcdef0123456789abcdef" ,
"Phone" : "13412341234" ,
"Enable" : True
}
# 对现有的 user 字典的所有键进行排序,并根据排序后的顺序重新构建 OrderedDict
user = OrderedDict ( [
( key , user [ key ] )
for key in sorted ( user )
] )
json = dumps ( user )
print ( json )

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

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

from collections import OrderedDict json = '{"Enable": "true", "Password": "0123456789abcdef0123456789abcdef", "Phone": "13412341234", "Uid": 100000, "Username": "hiiragi"}' user = loads(json, object_hook=OrderedDict)

三、C# 下的实现

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

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

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

先创建一个User模型类:

Username = "hiiragi", Password = "0123456789abcdef0123456789abcdef", Phone = "13412341234", Enable = true string json = JsonConvert.SerializeObject(user); Console.WriteLine(json);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using Newtonsoft . Json ;
static void Main ( )
{
User user = new User ( )
{
Uid = 100000 ,
Username = "hiiragi" ,
Password = "0123456789abcdef0123456789abcdef" ,
Phone = "13412341234" ,
Enable = true
} ;
string json = JsonConvert . SerializeObject ( user ) ;
Console . WriteLine ( json ) ;
}

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

Gets or sets the order of serialization of a member.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class User
{
[ JsonProperty ( Order = 4 ) ]
public int Uid { set ; get ; }
[ JsonProperty ( Order = 5 ) ]
public string Username { set ; get ; }
[ JsonProperty ( Order = 2 ) ]
public string Password { set ; get ; }
[ JsonProperty ( Order = 3 ) ]
public string Phone { set ; get ; }
[ JsonProperty ( Order = 1 ) ]
public bool Enable { set ; get ; }
}

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类所在的命名空间)

using Newtonsoft.Json.Serialization; public class OrderedContractResolver : DefaultContractResolver protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) return base.CreateProperties(type, memberSerialization).OrderBy(u => u.PropertyName).ToList();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System ;
using System . Collections . Generic ;
using System . Linq ;
using Newtonsoft . Json ;
using Newtonsoft . Json . Serialization ;
public class OrderedContractResolver : DefaultContractResolver
{
protected override IList < JsonProperty > CreateProperties ( Type type , MemberSerialization memberSerialization )
{
return base . CreateProperties ( type , memberSerialization ) . OrderBy ( u = > u . PropertyName ) . ToList ( ) ;
}
}

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

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

Username = "hiiragi", Password = "0123456789abcdef0123456789abcdef", Phone = "13412341234", Enable = true string json = JsonConvert.SerializeObject(user, new JsonSerializerSettings() ContractResolver = new OrderedContractResolver() Console.WriteLine(json);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
User user = new User ( )
{
Uid = 100000 ,
Username = "hiiragi" ,
Password = "0123456789abcdef0123456789abcdef" ,
Phone = "13412341234" ,
Enable = true
} ;
string json = JsonConvert . SerializeObject ( user , new JsonSerializerSettings ( )
{
ContractResolver = new OrderedContractResolver ( )
} ) ;
Console . WriteLine ( json ) ;

本篇文章主要讨论了在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