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