1. 概述
JSON (JavaScript Object Notation)是一种使用广泛的轻量数据格式. Python标准库中的json模块提供了JSON数据的处理功能.
Python中一种非常常用的基本数据结构就是字典(Dictionary). 它的典型结构如下:
1
2
3
4
5
6
|
d = { 'a' : 123 , 'b' : { 'x' : [ 'A' , 'B' , 'C' ] } } |
而JSON的结构如下:
1
2
3
4
5
6
|
{ "a" : 123, "b" : { "x" : [ "A" , "B" , "C" ] } } |
可以看到, Dictionary和JSON非常接近, 而Python中的json库提供的主要功能, 也是两者之间的转换.
2. 读取JSON
json.loads方法可以将包含了一个JSON数据的str, bytes或者bytearray对象, 转化为一个Python Dictionary. 它的完型接口签名如下:
2.1 最简单的例子
json.loads最基本的使用方式就是将一个包含JSON数据的str传递给这个方法:
1
2
|
>>> json.loads( '{"a": 123}' ) { 'a' : 123 } |
注意
在Python中, str值可以放在一对单引号中, 也可以放在一对双引号中:
1
2
|
>>> 'ABC' = = "ABC" True |
所以, 在定义Dictionary的str类型的键和值的时候, 使用单引号或者双引号都是合法和等价的:
1
2
|
>>> { "a" : 'ABC' } = = { 'a' : "ABC" } True |
但是, 在JSON中, 字符串数据只能放在双引号中, 因而json.loads方法处理的字符串的JSON内容中, 字符串必须使用双引号. 否则就会发生解码错误:
>>> json.loads("{'a': 123}")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py", line 354, in loads
return _default_decoder.decode(s)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/decoder.py", line 339, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/decoder.py", line 355, in raw_decode
obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
如果被处理的Python字符串是包含在双引号中的, 那么JSON中的双引号就需要转义:
1
2
|
>>> json.loads( "{\"a\": 123}" ) { 'a' : 123 } |
2.2 bytes和bytearray数据
对于内容是JSON数据的bytes和bytearray, json.loads方法也可以处理:
1
2
3
4
|
>>> json.loads( '{"a": 123}' .encode( 'UTF-8' )) { 'a' : 123 } >>> json.loads(bytearray( '{"a": 123}' , 'UTF-8' )) { 'a' : 123 } |
2.3 编码格式
json.loads的第二个参数是encoding没有实际作用.
由于Python 3中str类型总是使用UTF-8编码, 所以s参数为str类型时, json.loads方法自动使用UTF-8编码. 并且, str不能以BOM字节开头.
当s参数为bytes或者bytearray时, json.loads方法会自动判断为UTF-8, UTF-16还是UTF-32编码. 默认也是将其按照UTF-8编码转化为str对象进行后续处理.
2.4 数据类型转换
JSON可以表示四种主类型数据
- 1.字符串 string
- 2.数字 number
- 3.布尔类 boolean
- 4.空值 null
以及两结数据结构
- 1.对象 object
- 2.数组 array
默认实现中, JSON和Python之间的数据转换对应关系如下表:
JSON | Python |
---|---|
object | dict |
array | list |
string | str |
number (int) | int |
number (real) | float |
true | True |
false | False |
null | None |
实际转换情况如下例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
>>> json.loads(""" ... { ... "obj": { ... "str": "ABC", ... "int": 123, ... "float": -321.89, ... "bool_true": true, ... "bool_false": false, ... "null": null, ... "array": [1, 2, 3] ... } ... }""") {'obj': {'str': 'ABC', 'int': 123, 'float': -321.89, 'bool_true': True, 'bool_false': False, 'null': None, 'array': [1, 2, 3]}} |
对于JSON中数字number类型的数据, 有以下几点需要注意:
1.JSON中的实数real number类型的精度不能超过Python中的float类型的精度范围, 否则就有精度损失. 如下例:
1
2
|
>>> json.loads( '3.141592653589793238462643383279' ) 3.141592653589793 |
2.JSON标准不包括非数字NaN, 正无穷Infinity和负无穷-Infinity, 但是json.loads方法默认会将JSON字符串中的NaN, Infinity, -Infinity转化为Python中的float('nan'), float('inf')和float('-inf'). 注意, 这里JSON中的NaN, Infinity, -Infinity必须大小写正确并且拼写完整. 如下例
1
2
|
>>> json.loads( '{"inf": Infinity, "nan": NaN, "ninf": -Infinity}' ) { 'inf' : inf, 'nan' : nan, 'ninf' : - inf} |
2.5 自定义JSON对象转换类型
json.loads默认将JSON中的对象数据转化为Dictionary类型, object_hook参数可以用来改变构造出的对象.
object_hook接受一个函数, 这个函数的输入参数为JSON中对象数据转化出的Dictionary对象, 其返回值则为自定义的对象. 如下例所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
>>> class MyJSONObj: ... def __init__( self , x): ... self .x = x ... >>> def my_json_obj_hook(data): ... print ( 'obj_hook data: %s' % data) ... return MyJSONObj(data[ 'x' ]) ... >>> result = json.loads( '{"x": 123}' , object_hook = my_json_obj_hook) obj_hook data: { 'x' : 123 } >>> type (result) < class '__main__.MyJSONObj' > >>> result.x 123 |
当JSON中的对象有嵌套时, json.loads方法会按照深度优先的方式遍历对象树, 将各层的对象数据传递给object_hook. 叶节点的JSON对象构造出的Python对象, 会作为父节点的一个值, 传递给父节点的object_hook方法. 如下例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
>>> class MyJSONObj: ... def __init__( self , x, y): ... self .x = x ... self .y = y ... >>> def my_json_obj_hook(data): ... print ( 'obj_hook data: %s' % data) ... return MyJSONObj( * * data) ... >>> result = json.loads( '{"x": {"x": 11, "y": 12}, "y": {"x": 21, "y":22}}' , object_hook = my_json_obj_hook) obj_hook data: { 'x' : 11 , 'y' : 12 } obj_hook data: { 'x' : 21 , 'y' : 22 } obj_hook data: { 'x' : <__main__.MyJSONObj object at 0x10417ef28 >, 'y' : <__main__.MyJSONObj object at 0x10417ed68 >} |
除了object_hook参数以外, 还有一个object_pairs_hook参数. 这个参数同样可以用来改变json.loads方法构造出的Python对象的类型. 这个参数和object_hook的不同, 在于传入的方法所接收到的输入数据不是一个Dictionary, 而是一个包含tuple的list. 每个tuple都有两个元素, 第一个元素是JSON数据中的键, 第二个元素是这个键对应的值. 如JSON对象
1
2
3
4
|
{ "a" : 123, "b" : "ABC" } |
对应的输入数据是
[
('a': 123),
('b', 'ABC')
]
当调用json.loads方法时, 同时指定object_hook和object_pairs_hook, object_pairs_hook会覆盖object_hook参数.
2.6 自定义JSON数字转换类型
默认实现中, JSON中的实数被转换为Python的float类型, 整数被转换为int或者long类型. 类似object_hook, 我们可以通过parse_float和parse_int参数指定自定义的转换逻辑. 这两个方法的输入参数为表示JSON实数或者整数的字符串. 下例中, 我们将实数转换为numpy.float64, 将整数转换为numpy.int64:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
>>> def my_parse_float(f): ... print ( '%s(%s)' % ( type (f), f)) ... return numpy.float64(f) ... >>> def my_parse_int(i): ... print ( '%s(%s)' % ( type (i), i)) ... return numpy.int64(i) ... >>> result = json.loads( '{"i": 123, "f": 321.45}' , parse_float = my_parse_float, parse_int = my_parse_int) < type 'str' >( 123 ) < type 'str' >( 321.45 ) >>> type (result[ 'i' ]) < type 'numpy.int64' > >>> type (result[ 'f' ]) < type 'numpy.float64' > |
2.6.1 自定义NaN, Infinity和-Infinity转换类型
由于标准JSON数据不支持NaN, Infinity和-Infinity, 所以parse_float并不会接收到这几个值. 当需要自定义这几个值转换的对象的时候, 就需要使用另外一个接口parse_constant. 比如下例中, 将这几个值同样转换为numpy.float64类型:
1
2
3
4
5
6
7
8
9
10
11
12
|
>>> def my_parse_constant(data): ... print ( '%s(%s)' % ( type (data), data)) ... return numpy.float64(data) ... >>> result = json.loads( '{"inf": Infinity, "nan": NaN, "ninf": -Infinity}' , parse_constant = my_parse_constant) < type 'str' >(Infinity) < type 'str' >(NaN) < type 'str' >( - Infinity) >>> result[ 'inf' ] inf >>> type (result[ 'inf' ]) < type 'numpy.float64' > |
2.7 非对象顶级值
根据JSON规范, 一个JSON数据中, 可以只包含一个值, 而不是一个完整的对象. 这个值可以是一个字符串, 一个数字, 布尔值, 空值, 或者一个数组. 除了这三种JSON规范中给出的类型, 还可以是NaN, Infinity或者-Infinity:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
>>> json.loads( '"hello"' ) 'hello' >>> json.loads( '123' ) 123 >>> json.loads( '123.34' ) 123.34 >>> json.loads( 'true' ) True >>> json.loads( 'false' ) False >>> print (json.loads( 'null' )) None >>> json.loads( '[1, 2, 3]' ) [ 1 , 2 , 3 ] |
2.8 重复键名
在同一层级JSON对象中, 不应当出现重复的键名, 不过JSON规范中没有给出这种情况的处理标准. 在json.loads中, 当JSON数据中有重复键名, 则后面的键值会覆盖前面的:
1
2
|
>>> json.loads( '{"a": 123, "b": "ABC", "a": 321}' ) { 'a' : 321 , 'b' : 'ABC' } |
2.9 处理JSON数据文件
当JSON数据是保存在一个文件中的时候, json.load方法可以用来从这个文件中读取数据, 并转换为Python对象. json.load方法的第一个参数就是指向JSON数据文件的文件类型对象.
比如/tmp/data.json文件的内含如下:
1
2
3
4
|
{ "a" : 123, "b" : "ABC" } |
可以使用下例中的代码来读取并转化文件中的JSON数据:
1
2
3
4
|
>>> with open ( '/tmp/data.json' ) as jf: ... json.load(jf) ... {u 'a' : 123 , u 'b' : u 'ABC' } |
除了文件类型的对象, 只要是实现了read方法的类文件对象, 都可以作为fp参数, 比如下例中的io.StringIO:
1
2
3
|
>>> sio = io.StringIO( '{"a": 123}' ) >>> json.load(sio) { 'a' : 123 } |
json.load方法的其他参数的意义和使用方法和上文中的json.loads相同, 这里不再赘述.
3 生成JSON
json.dumps方法可以将Python对象转换为一个表示JONS数据的字符串. 它的完整接口签名如下:
它的第一个参数obj即为要转换的数据对象.
1
2
|
>>> json.dumps({ 'a' : 123 , 'b' : 'ABC' }) '{"a": 123, "b": "ABC"}' |
3.1 编码格式
json.dumps的ensure_ascii参数用来控制生成的JSON字符串的编码. 其默认值为True, 此时, 所有的非ASCII码字条都会转义. 如果不希望自动进行转义, 则会保持原有编码, 限UTF-8. 如下例所示:
1
2
3
4
|
>>> json.dumps({ '数字' : 123 , '字符' : '一二三' }) '{"\\u6570\\u5b57": 123, "\\u5b57\\u7b26": "\\u4e00\\u4e8c\\u4e09"}' >>> json.dumps({ '数字' : 123 , '字符' : '一二三' }, ensure_ascii = False ) '{"数字": 123, "字符": "一二三"}' |
3.2 数据类型转换
在默认实现中, json.dumps可以处理的Python对象, 及其所有的属性值, 类型必须为dict, list, tuple, str, float或者int. 这些类型与JSON的数据转换关系如下表:
Python | JSON |
---|---|
dict | object |
list, tuple | array |
str | string |
int, float, int-&float-derived emuns | number |
True | true |
False | false |
None | null |
实际转换情况如下示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
>>> json.dumps( ... { ... 'str' : 'ABC' , ... 'int' : 123 , ... 'float' : 321.45 , ... 'bool_true' : True , ... 'bool_false' : False , ... 'none' : None , ... 'list' : [ 1 , 2 , 3 ], ... 'tuple' : [ 12 , 34 ] ... } ... ) '{"str": "ABC", "int": 123, "float": 321.45, "bool_true": true, "bool_flase": false, "none": null, "list": [1, 2, 3], "tuple": [12, 34]}' |
虽然JSON标准规范不支持NaN, Infinity和-Infinity, 但是json.dumps的默认实现会将float('nan'), float('inf')和float('-inf')转换为常量NaN, Infinity, 和-Infinity. 如下例所示:
1
2
3
4
5
6
7
8
|
>>> json.dumps( ... { ... 'nan' : float ( 'nan' ), ... 'inf' : float ( 'inf' ), ... '-inf' : float ( '-inf' ) ... } ... ) '{"nan": NaN, "inf": Infinity, "-inf": -Infinity}' |
由于这些常量可能会导致生成的JSON字符串不能被其他的JSON实现处理, 为了防止这种情况出现, 可以将json.dumps的allow_nan参数设置为True. 此时, 当处理的Python对象中出现这些值时, json.dumps方法会抛出异常.
3.3 循环引用
json.dumps方法会检查Python对象中是否有循环引用, 如果发现了循环引用, 就会抛出异常. 如下例所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
>>> circular_obj = {} >>> circular_obj[ 'self' ] = circular_obj >>> circular_obj { 'self' : {...}} >>> json.dumps(circular_obj) Traceback (most recent call last): File "<stdin>" , line 1 , in <module> File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py" , line 231 , in dumps return _default_encoder.encode(obj) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py" , line 199 , in encode chunks = self .iterencode(o, _one_shot = True ) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py" , line 257 , in iterencode return _iterencode(o, 0 ) ValueError: Circular reference detected |
如果不希望json.dumps方法检查循环引用, 可以将参数check_circular设置为False. 但如果此时Python对象中有循环引用, 有可能发生递归嵌套过深的错误或者其他错误, 这么做是比较危险的. 如下例所示:
1
2
3
4
5
6
7
8
9
10
|
>>> json.dumps(circular_obj, check_circular = False ) Traceback (most recent call last): File "<stdin>" , line 1 , in <module> File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py" , line 238 , in dumps * * kw).encode(obj) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py" , line 199 , in encode chunks = self .iterencode(o, _one_shot = True ) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py" , line 257 , in iterencode return _iterencode(o, 0 ) RecursionError: maximum recursion depth exceeded while encoding a JSON object |
3.4 JSON字符串输出格式
json.dumps方法的indent参数可以用来控制JSON字符串的换行和缩进效果.
indent参数默认值为None. 此时, JSON字符串不会有换行和缩进效果. 如下示:
1
2
|
>>> print (json.dumps({ 'a' : 123 , 'b' : { 'x' : 321 , 'y' : 'ABC' }})) { "a" : 123 , "b" : { "x" : 321 , "y" : "ABC" }} |
当indent为0或者负数时, JSON字符会包含换行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
>>> print (json.dumps({ 'a' : 123 , 'b' : { 'x' : 321 , 'y' : 'ABC' }}, indent = - 1 )) { "a" : 123 , "b" : { "x" : 321 , "y" : "ABC" } } >>> print (json.dumps({ 'a' : 123 , 'b' : { 'x' : 321 , 'y' : 'ABC' }}, indent = 0 )) { "a" : 123 , "b" : { "x" : 321 , "y" : "ABC" } } |
而当indent为正整数时, 除了换行, JSON还会以指定数量的空格为单位在对象层次间进行缩进:
1
2
3
4
5
6
7
8
|
>>> print (json.dumps({ 'a' : 123 , 'b' : { 'x' : 321 , 'y' : 'ABC' }}, indent = 2 )) { "a" : 123 , "b" : { "x" : 321 , "y" : "ABC" } } |
indent还可以是str, 此时, JSON会以str内容为单位进行缩进, 比如制表符\t:
1
2
3
4
5
6
7
8
|
>>> print (json.dumps({ 'a' : 123 , 'b' : { 'x' : 321 , 'y' : 'ABC' }}, indent = '\t' )) { "a" : 123 , "b" : { "x" : 321 , "y" : "ABC" } } |
json.dumps的另外一个参数separators可以用来设置输出的分隔符. 这个参数的值应当是一个有两个元素的tuple. 其第一个值为成员间的分隔符, 第二个值为键值之间的分隔符. 其默认值也会随上文中的indent参数影响. 当indent为None时, separators的默认值为(', ', ': '), 即分隔符后都有一个空格. 当indent不为None时, 其默认值则为(',', ':'), 即只有键值间分隔符后会有一个空格, 而元素间分隔符则不带空格, 因为此时会有换行.
separators参数的一种可能的使用场景是希望移除所有的非必要格式字符, 以此来减小JSON字符串的大小. 此时可以将separator设置为(',', ';'), 并不设置indent参数, 或者将其显式设置为None:
1
2
|
>>> print (json.dumps({ 'a' : 123 , 'b' : { 'x' : 321 , 'y' : 'ABC' }}, indent = None , separators = ( ',' , ':' ))) { "a" : 123 , "b" :{ "x" : 321 , "y" : "ABC" }} |
3.5 转换自定义Python对象
json.dumps的默认实现只能转换Dictionary类型的对象. 如果想要转换自定义对象, 需要使用default参数. 这个参数接收一个函数, 这个函数的参数是一个要转换的Python对象, 返回值是能够表示这个Python对象的Dictionary对象. default函数会从对象引用树的顶层开始, 逐层遍历整个对象引用树. 因此, 不用自己实现对象树的遍历逻辑, 只需要处理当前层次的对象. 如下例所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
>>> class MyClass: ... def __init__( self , x, y): ... self .x = x ... self .y = y ... >>> def my_default(o): ... if isinstance (o, MyClass): ... print ( '%s.y: %s' % ( type (o), o.y)) ... return { 'x' : o.x, 'y' : o.y} ... print (o) ... return o ... >>> obj = MyClass(x = MyClass(x = 1 , y = 2 ), y = 11 ) >>> json.dumps(obj, default = my_default) < class '__main__.MyClass' >.y: 11 < class '__main__.MyClass' >.y: 2 '{"x": {"x": 1, "y": 2}, "y": 11}' |
3.6 非字符串类型键名
在Python中, 只是可哈希(hashable)的对象和数据都可以做为Dictionary对象的键, 而JSON规范中则只能使用字符串做为键名. 所以在json.dumps的实现中, 对这个规则进行了检查, 不过键名允许的范围有所扩大, str, int, float, bool和None类型的数据都可以做为键名. 不过当键名非str的情况时, 键名会转换为对应的str值. 如下例:
1
2
3
4
5
6
7
8
9
10
11
|
>>> json.dumps( ... { ... 'str' : 'str' , ... 123 : 123 , ... 321.54 : 321.54 , ... True : True , ... False : False , ... None : None ... } ... ) '{"str": "str", "123": 123, "321.54": 321.54, "true": true, "false": false, "null": null}' |
而当出现其他类型的键名时, 默认出抛出异常:
1
2
3
4
5
6
7
8
9
10
|
>>> json.dumps({(1,2): 123}) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py", line 231, in dumps return _default_encoder.encode(obj) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 199, in encode chunks = self.iterencode(o, _one_shot=True) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 257, in iterencode return _iterencode(o, 0) TypeError: keys must be a string |
json.dumps的skipkeys参数可以改变这个行为. 当将skipkeys设置为True时, 遇到非法的键名类型, 不会抛出异常, 而是跳过这个键名:
1
2
|
>>> json.dumps({( 1 , 2 ): 123 }, skipkeys = True ) '{}' |
3.7 生成JSON文件
当需要将生成的JSON数据保存到文件时, 可以使用json.dump方法. 这个方法比json.dumps多了一个参数fp, 这个参数就是用来保存JSON数据的文件对象. 比如, 下例中的代码
1
2
3
|
>>> with open ( '/tmp/data.json' , mode = 'a' ) as jf: ... json.dump({ 'a' : 123 }, jf) ... |
就会将JSON数据写入到/tmp/data.json文件里. 代码执行完后, 文件内容为
1
2
3
4
5
6
|
{ "a" : 123 } json.dump方法也可以接受其他类文件对象: >>> sio = io.StringIO() >>> json.dump({ 'a' : 123 }, sio) >>> sio.getvalue() '{"a": 123}' |
json.dump的其他参数和json.dumps的用法相同, 这里不再赘述.
4 JSON解码和编码类实现
json.loads, json.load, json.dumps和json.dump这四个方法是通过json.JSONDecoder和json.JSONEncoder这两个类来完成各自的任务的. 所以也可以直接使用这两个类来完成前文描述的功能:
1
2
3
4
|
>>> json.JSONDecoder().decode( '{"a": 123}' ) { 'a' : 123 } >>> json.JSONEncoder().encode({ 'a' : 123 }) '{"a": 123}' |
json.loads, json.load, json.dumps和json.dump这个四个方法的参数主要都是传递给了json.JSONDecoder和json.JSONEncoder的构造方法, 所以使用这些方法可以满足绝大部分需求. 当需要自定义json.JSONDecoder和json.JSONEncoder子类的时候, 只需要将子类传递给cls参数. 同时, 这些方法都有**kw参数. 当自定义实现类的构造函数需要标准参数列表之外的新参数时, 这个参数就会将新参数传递给实现类的构造方法.
5 相关资源
- JSON
- The JavaScript Object Notation (JSON) Data Interchange Format - RFC 4627
- json — JSON encoder and decoder
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://www.cnblogs.com/mithrilon/p/8954690.html