所谓数据序列化(Data Serialization), 就是将某个对象的状态信息转换为可以存储或传输的形式的过程。 那么,为什么要进行序列化?
- 首先,为了方便数据存储;
- 其次,为了方便数据传递。
在数据序列化期间,某个对象的当前状态被写入到临时或永久存储区。随后,可以把序列化到存储区的数据(通过网络)传输出去,然后进行反序列化,重新创建该对象。 运行在节点A上的某个对象X的当前状态,可以理解为保存在节点A的内存里的某个结构体。那么要把节点A上的对象X的状态信息传递到节点B上,把对象X的状态信息从内存中dump出来并序列化是必不可少的。支持数据序列化的常见格式有XML, JSON 和YAML。接下来本系列将首先介绍一下JSON。
1. 什么是JSON?
JSON是JavaScript Object Notation的缩写。简单来说,JSON是一种轻量级的数据交换格式,易于人类阅读和书写,同时也易于机器解析和生成。它基于JavaScript语言而实现, 是open ECMAScript standard的一个子集。 JSON采用完全独立于语言的文本格式,但也使用了类似于C语言家族的习惯。这些特性使得JSON成为了一种理想的数据交换格式。
特别注意: JSON的字符串必须用双引号引用起来。 (因为后面会讲到YAML, YAML的字符串没有这样的限制)
2. 构建JSON的结构
- A collection of name/value pairs(键/值对集合), 即对象(object), 也就是字典(dict)。使用{ }表示,与Python的dict类似。
- An ordered list of values (值的有序表),即数组(array)。使用[ ]表示,与Python的list类似。
注意:上面的截图来源戳这里。
2.1 对象(object)
在其他语言中,对象(object)又称为字典(dict),纪录(record),结构(struct),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。在JSON中,通常把键/值对集合称之为对象(Object)(P.S. 本人习惯于字典的叫法)。对象是一个无序的“‘键/值’对”集合。一个对象以“{”开始,“}”结束。每个“键”后跟一个“:”(冒号);“‘键/值’ 对”之间使用“,”(逗号)分隔。例如:
1 var Goddess = {
2 "FirstName" : "Grace",
3 "LastName" : "Liu",
4 "Age" : "18"
5 };
2.2 数组(array)
数组很好理解,跟C语言的数组没什么不同,跟Python的list一样。数组是值(value)的有序集合。一个数组以“[”(左中括号)开始,“]”(右中括号)结束。值之间使用“,”(逗号)分隔。例如:
1 var Students = [
2 {"name":"John", "age":"23", "city":"Agra"},
3 {"name":"Steve", "age":"28", "city":"Delhi"},
4 {"name":"Peter", "age":"32", "city":"Chennai"},
5 {"name":"Chaitanya", "age":"28", "city":"Bangalore"}
6 ];
3. 值(value)的类型
- 字符串(string)
- 数字(number)
- 对象(object(即字典))
- 数组(array)
- 布尔值(boolean)
- 空值(null)
3.1 字符串(string)
- 字符串(string)是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。
- 单个字符(character)即一个单独的字符串(character string)。
- 字符串(string)与C语言的字符串非常相似。
3.2 数值(number)
- 数值(number)也与C的数值非常相似。
- 不使用8进制和16进制编码。
3.3 对象(object)
对象(object)即字典(dict),参见2.1。
3.4 数组(array)
数组(array)即列表(list),参见2.2。
3.5 布尔值(boolean)
要么为真(true), 要么为假(false)。对应于Python中的True/False。 注意在Python中, 真/假的开头字母是大写,而JSON一律用小写。
3.6 空值(null)
JSON的空值用null表示,类似于C语言的NULL, Python语言的None,Go语言的nil。
P.S. 由3.5和3.6可以看出,JSON偏好使用一律小写的关键字。
4 在Python中使用JSON
4.1 JSON值类型 v.s. Python值类型
4.2 将Python对象序列化(serialize)为JSON格式的文本
Python提供了专门的模块json, 使用json.dump()或者json.dumps()就可以把一个Python对象序列化为JSON格式的文本。 有关json模块的具体用法,请参见这里。
- foo_python2json.py
1 #!/usr/bin/python3
2
3 """ Serialize a Python Object by using json.dumps() """
4
5 import sys
6 import json
7
8 obj = {
9 "students":
10 [
11 {
12 "name": "John",
13 "age": 23,
14 "city": "Agra",
15 "married": False,
16 "spouse": None
17 },
18 {
19 "name": "Steve",
20 "age": 28,
21 "city": "Delhi",
22 "married": True,
23 "spouse": "Grace"
24 },
25 {
26 "name": "Peter",
27 "age": 32,
28 "city": "Chennai",
29 "married": True,
30 "spouse": "Rachel"
31 }
32 ]
33 }
34
35 def main(argc, argv):
36 if argc != 2:
37 sys.stderr.write("Usage: %s <json file to save obj>\n" % argv[0])
38 return 1
39
40 with open(argv[1], 'w') as f:
41 txt = json.dumps(obj, indent=4)
42 print("DEBUG> " + str(type(obj)))
43 print("DEBUG> " + str(obj))
44 print("DEBUG> " + str(type(txt)))
45 print("DEBUG> " + txt)
46 f.write(txt + '\n')
47
48 return 0
49
50 if __name__ == '__main__':
51 sys.exit(main(len(sys.argv), sys.argv))
- Run foo_python2json.py
huanli$ rm -f /tmp/foo.json
huanli$ ./foo_python2json.py /tmp/foo.json
DEBUG> <class 'dict'>
DEBUG> {'students': [{'spouse': None, 'age': 23, 'city': 'Agra', 'name': 'John', 'married': False}, {'spouse': 'Grace', 'age': 28, 'city': 'Delhi', 'name': 'Steve', 'married': True}, {'spouse': 'Rachel', 'age': 32, 'city': 'Chennai', 'name': 'Peter', 'married': True}]}
DEBUG> <class 'str'>
DEBUG> {
"students": [
{
"spouse": null,
"age": 23,
"city": "Agra",
"name": "John",
"married": false
},
{
"spouse": "Grace",
"age": 28,
"city": "Delhi",
"name": "Steve",
"married": true
},
{
"spouse": "Rachel",
"age": 32,
"city": "Chennai",
"name": "Peter",
"married": true
}
]
}
huanli$
huanli$ cat -n /tmp/foo.json
1 {
2 "students": [
3 {
4 "spouse": null,
5 "age": 23,
6 "city": "Agra",
7 "name": "John",
8 "married": false
9 },
10 {
11 "spouse": "Grace",
12 "age": 28,
13 "city": "Delhi",
14 "name": "Steve",
15 "married": true
16 },
17 {
18 "spouse": "Rachel",
19 "age": 32,
20 "city": "Chennai",
21 "name": "Peter",
22 "married": true
23 }
24 ]
25 }
huanli$
4.3 将JSON格式的文本反序列化(deserialize)为Python对象
使用json.load()或者json.loads()就可以将一个JSON格式的文本反序列化为一个Python对象。
- foo_json2python.py
1 #!/usr/bin/python3
2
3 """ Deserialize JSON text to a Python Object by using json.loads() """
4
5 import sys
6 import json
7
8 def main(argc, argv):
9 if argc != 2:
10 sys.stderr.write("Usage: %s <json file>\n" % argv[0])
11 return 1
12
13 with open(argv[1], 'r') as f:
14 txt = ''.join(f.readlines())
15 obj = json.loads(txt)
16 print("DEBUG> " + str(type(txt)))
17 print("DEBUG> " + txt)
18 print("DEBUG> " + str(type(obj)))
19 print("DEBUG> " + str(obj))
20
21 return 0
22
23 if __name__ == '__main__':
24 sys.exit(main(len(sys.argv), sys.argv))
- Run foo_json2python.py
huanli$ cat -n /tmp/foo.json
1 {
2 "students": [
3 {
4 "spouse": null,
5 "age": 23,
6 "city": "Agra",
7 "name": "John",
8 "married": false
9 },
10 {
11 "spouse": "Grace",
12 "age": 28,
13 "city": "Delhi",
14 "name": "Steve",
15 "married": true
16 },
17 {
18 "spouse": "Rachel",
19 "age": 32,
20 "city": "Chennai",
21 "name": "Peter",
22 "married": true
23 }
24 ]
25 }
huanli$
huanli$ ./foo_json2python.py /tmp/foo.json
DEBUG> <class 'str'>
DEBUG> {
"students": [
{
"spouse": null,
"age": 23,
"city": "Agra",
"name": "John",
"married": false
},
{
"spouse": "Grace",
"age": 28,
"city": "Delhi",
"name": "Steve",
"married": true
},
{
"spouse": "Rachel",
"age": 32,
"city": "Chennai",
"name": "Peter",
"married": true
}
]
}
DEBUG> <class 'dict'>
DEBUG> {'students': [{'city': 'Agra', 'name': 'John', 'married': False, 'spouse': None, 'age': 23}, {'city': 'Delhi', 'name': 'Steve', 'married': True, 'spouse': 'Grace', 'age': 28}, {'city': 'Chennai', 'name': 'Peter', 'married': True, 'spouse': 'Rachel', 'age': 32}]}
huanli$
直接使用json.load()也可以,例如:
huanli$ python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
...<snip>....................................
>>> import json
>>> fd = open("/tmp/foo.json", "r")
>>> obj = json.load(fd)
>>> type(obj)
<class 'dict'>
>>> obj
{'students': [{'name': 'John', 'married': False, 'age': 23, 'city': 'Agra', 'spouse': None}, {'name': 'Steve', 'married': True, 'age': 28, 'city': 'Delhi', 'spouse': 'Grace'}, {'name': 'Peter', 'married': True, 'age': 32, 'city': 'Chennai', 'spouse': 'Rachel'}]}
>>>
4.4 序列化/反序列化用户定制的Python对象
在Python中,有一个模块pickle能把所有的Python对象都序列化。例如:
>>> import pickle
>>>
>>> a = 1 + 2j
>>> s = pickle.dumps(a)
>>> s
b'\x80\x03cbuiltins\ncomplex\nq\x00G?\xf0\x00\x00\x00\x00\x00\x00G@\x00\x00\x00\x00\x00\x00\x00\x86q\x01Rq\x02.'
>>> b = pickle.loads(s)
>>> b
(1+2j)
>>> b == a
True
>>>
但是,要把一个用户定制的Python对象序列化为JSON文本就没有这么容易了,不信请看:
>>> import json
>>> a = 1 + 2j
>>> type(a)
<class 'complex'>
>>> s = json.dumps(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib64/python3.6/json/__init__.py", line 231, in dumps
return _default_encoder.encode(obj)
File "/usr/lib64/python3.6/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib64/python3.6/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/usr/lib64/python3.6/json/encoder.py", line 180, in default
o.__class__.__name__)
TypeError: Object of type 'complex' is not JSON serializable
>>>
怎么办?
- 自己实现一个序列化/反序列化的hook;
- 然后交给json.encode()/json.decode()去处理。
4.4.1 序列化用户定制的Python对象
- foo_encode.py
1 #!/usr/bin/python3
2
3 import sys
4 import json
5
6 def encode_complex(z):
7 d_out = {}
8 if isinstance(z, complex):
9 d_out['__complex__'] = True
10 d_out['real'] = z.real
11 d_out['imag'] = z.imag
12 return d_out
13 else:
14 type_name = z.__class__.__name__
15 raise TypeError(f"Object of type '{type_name}' is not JSON serializable")
16
17 def main(argc, argv):
18 if argc != 3:
19 sys.stderr.write("Usage: %s <complex> <json file>\n" % argv[0])
20 return 1
21
22 z = complex(argv[1])
23 f = argv[2]
24 with open(f, 'w') as fd:
25 txt = json.dumps(z, indent=4, default=encode_complex)
26 fd.write(txt + '\n')
27
28 if __name__ == '__main__':
29 sys.exit(main(len(sys.argv), sys.argv))
- Run foo_encode.py
huanli$ rm -f /tmp/foo.json
huanli$ ./foo_encode.py '20+1.8j' /tmp/foo.json
huanli$ cat -n /tmp/foo.json
1 {
2 "__complex__": true,
3 "real": 20.0,
4 "imag": 1.8
5 }
huanli$
4.4.2 反序列化用户定制的Python对象
- foo_decode.py
1 #!/usr/bin/python3
2
3 import sys
4 import json
5
6 def decode_complex(dct):
7 if ('__complex__' in dct) and (dct['__complex__'] is True):
8 return complex(dct['real'], dct['imag'])
9 return dct
10
11 def main(argc, argv):
12 if argc != 2:
13 sys.stderr.write("Usage: %s <json file>\n" % argv[0])
14 return 1
15
16 f = argv[1]
17 with open(f, 'r') as fd:
18 txt = ''.join(fd.readlines())
19 z = json.loads(txt, object_hook=decode_complex)
20 print(type(z))
21 print(z)
22
23 if __name__ == '__main__':
24 sys.exit(main(len(sys.argv), sys.argv))
- Run foo_decode.py
huanli$ cat -n /tmp/foo.json
1 {
2 "__complex__": true,
3 "real": 20.0,
4 "imag": 1.8
5 }
huanli$ ./foo_decode.py /tmp/foo.json
<class 'complex'>
(20+1.8j)
5. JSON的注释
JSON本身并不支持注释,也就是说,不能使用#, //, /* ... */之类的给JSON文件加注释。但是,可以使用一种变通的办法,如果非要给JSON文件加注释的话。因为在JSON中,如果多个key相同,最后一个key被认为是有效的。例如:
- qian.json
{
"a": "# comments for field a: this is a string",
"a": "qian",
"b": "# comments for field b: this is a number",
"b": 35,
"c": "# comments for field c: this is a boolean",
"c": true,
"d": "# comments for field d: this is a null",
"d": null,
"e": "# comments for field e: this is an array",
"e": [1, "abc", false, null],
"f": "# comments for filed f: this is an object",
"f": {"name": "qian", "age": 35}
}
- 使用4.3的foo_json2python.py解析如下
$ ./foo_json2python.py /tmp/qian.json
DEBUG> <class 'str'>
DEBUG> {
"a": "# comments for field a: this is a string",
"a": "qian",
"b": "# comments for field b: this is a number",
"b": 35,
"c": "# comments for field c: this is a boolean",
"c": true,
"d": "# comments for field d: this is a null",
"d": null,
"e": "# comments for field e: this is an array",
"e": [1, "abc", false, null],
"f": "# comments for filed f: this is an object",
"f": {"name": "qian", "age": 35}
}
DEBUG> <class 'dict'>
DEBUG> {'a': 'qian', 'b': 35, 'c': True, 'd': None, 'e': [1, 'abc', False, None], 'f': {'name': 'qian', 'age': 35}}
小结:
JSON作为一种支持数据序列化的文本格式,简单易用,概括起来就是:
- It is light-weight 轻量级(相对于XML来说)
- It is language independent 与语言无关
- Easy to read and write 读写容易
- Text based, human readable data exchange format 基于文本的人类可读的数据交换格式
注意绝大多数语言都支持JSON, 所以进行数据序列化和反序列化非常容易。 本文以Python语言为例,给出了序列化和反序列化的代码样例。 默认情况下,我们使用json.dump()/json.dumps()和json.load()/json.loads()即可;但是,对于用户定制的对象类型,则需要使用json.encode()和json.decode()。
参考资料:
- Introducing JSON
- What is JSON
- JSON Tutorial: Learn JSON in 10 Minutes
- Working With JSON Data in Python
- Comparison between JSON and YAML for data serialization
后记:
如果一个JSON文件写得不够clean, 不妨使用jsonfmt.py进行格式化。另外,推荐使用工具jq (Command-line JSON processor), e.g.
$ jq -r <<< '{"name":"foo", "id": 123}'
{
"name": "foo",
"id": 123
}
$ jq -r .id <<< '{"name":"foo", "id": 123}'
123
$ jq -r .id,.name <<< '{"name":"foo", "id": 123}'
123
foo