所谓数据序列化(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> " % 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 + ' ') 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> " % 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'x80x03cbuiltins complex qx00G?xf0x00x00x00x00x00x00G@x00x00x00x00x00x00x00x86qx01Rqx02.' >>> 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> " % 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 + ' ') 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> " % 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