- json JavaScript对象记法
json模块提供了一个与pickle类似的API,可以行化表示,被称为JavaScript对象记法(JavaScript Object Notation,JSON)。不同于pickle,JSON有一个优点,它有多种语言的实现(特别是JavaScript)。JSON对于RESTAPI中Web服务器和客户之间的通信使用最广泛,不过也可以用于满足其他应用间的通信需求。
1.1 编码和解码简单数据类型
默认的,编码器理解Python的一些内置类型(即str、int、float、list、
tuple和dict)。
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))
data_string = json.dumps(data)
print('JSON:', data_string)
对值编码时,表面上类似于Python的repr()输出。
编码然后再重新解码时,可能不会得到完全相同的对象类型。
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA :', data)
data_string = json.dumps(data)
print('ENCODED:', data_string)
decoded = json.loads(data_string)
print('DECODED:', decoded)
print('ORIGINAL:', type(data[0]['b']))
print('DECODED :', type(decoded[0]['b']))
- 8
- 9
具体的,元组会变成列表。
1.2 人类可读和紧凑输出
JSON优于pickle的另一个好处是,JSON会生成人类可读的结果。dumps()函数接受多个参数从而使输出更容易理解。例如,sort_keys标志会告诉编码器按有序顺序而不是随机顺序输出字典的键。
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))
unsorted = json.dumps(data)
print('JSON:', json.dumps(data))
print('SORT:', json.dumps(data, sort_keys=True))
first = json.dumps(data, sort_keys=True)
second = json.dumps(data, sort_keys=True)
print('UNSORTED MATCH:', unsorted == first)
print('SORTED MATCH :', first == second)
- 8
- 9
- 10
排序后,会让人更容易的查看结果,而且还可以在测试中比较JSON输出。
对于高度嵌套的数据结构,还可以指定一个缩进(indent)值来得到格式美观的输出。
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))
print('NORMAL:', json.dumps(data, sort_keys=True))
print('INDENT:', json.dumps(data, sort_keys=True, indent=2))
- 1
- 2
- 3
- 4
- 5
当缩进是一个非负整数时,输出更类似于pprint的输出,数据结构中每一级的前导空格与缩进级别匹配。
这种详细输出会增加传输等量数据所需的字节数,所以生产环境中往往不使用这种输出。实际上,可以调整编码输出中分隔数据的设置,从而使其比默认格式更紧凑。
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
print('DATA:', repr(data))
print('repr(data) :', len(repr(data)))
plain_dump = json.dumps(data)
print('dumps(data) :', len(plain_dump))
small_indent = json.dumps(data, indent=2)
print('dumps(data, indent=2) :', len(small_indent))
with_separators = json.dumps(data, separators=(',', ':'))
print('dumps(data, separators):', len(with_separators))
- 8
- 9
- 10
dumps()的separators参数应当是一个元组,其中包含用来分隔列表中各项的字符串,以及分隔字典中键和值的字符串。默认为(’,’,’:’)。通过去除空白符,可以生成一个更为紧凑的输出。
1.3 编码字典
JSON格式要求字典的键是字符串。如果一个字典以非字符串类型作为键,那么对这个字典编码时,便会生成一个TypeError。要想绕开这个限制,一种办法是使用skipkeys参数告诉编码器跳过非串的键。
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0, ('d',): 'D tuple'}]
print('First attempt')
try:
print(json.dumps(data))
except TypeError as err:
print('ERROR:', err)
print()
print('Second attempt')
print(json.dumps(data, skipkeys=True))
- 8
- 9
- 10
这里不会产生一个异常,而是会忽略非串的键。
1.4 处理定制类型
目前为止,所有例子都使用Python的内置类型,因为这些类型得到了json的内置支持。通常还需要对定制类编码,有两种办法可以做到。假设以下代码清单中的类需要进行编码。
import json
class MyObj:
def __init__(self, s):
self.s = s
def __repr__(self):
return '<MyObj({})>'.format(self.s)
- 1
- 2
- 3
- 4
- 5
- 6
要对MyObj实例编码,一个简单的方法是定义一个函数,将未知类型转换为已知类型。这个函数并不需要具体完成编码,它只是将一个类型的对象转换为另一个类型。
import json
class MyObj:
def __init__(self, s):
self.s = s
def __repr__(self):
return '<MyObj({})>'.format(self.s)
obj = MyObj('instance value goes here')
print('First attempt')
try:
print(json.dumps(obj))
except TypeError as err:
print('ERROR:', err)
def convert_to_builtin_type(obj):
print('default(', repr(obj), ')')
# Convert objects to a dictionary of their representation
d = {
'__class__': obj.__class__.__name__,
'__module__': obj.__module__,
}
d.update(obj.__dict__)
return d
print()
print('With default')
print(json.dumps(obj, default=convert_to_builtin_type))
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
在convert_to_builtin_ type()中,json无法识别的类实例会被转换为字典,其中包含足够多的信息,如果程序能访问这个处理所需的Python模块,就能利用这些信息重新创建对象。
要对结果解码并创建一个MyObj()实例,可以使用loads()的objecthook参数关联解码器,从而可以从模块导入这个类,并将该类用来创建实例。对于从到来数据流解码的各个字典,都会调用object_hook,这就提供了一个机会,可以把字典转换为另外一种类型的对象。hook函数要返回调用应用要接收的对象而不是字典。
import json
def dict_to_object(d):
if '__class__' in d:
class_name = d.pop('__class__')
module_name = d.pop('__module__')
module = __import__(module_name)
print('MODULE:', module.__name__)
class_ = getattr(module, class_name)
print('CLASS:', class_)
args = {
key: value
for key, value in d.items()
}
print('INSTANCE ARGS:', args)
inst = class_(**args)
else:
inst = d
return inst
encoded_object = '''
[{"s": "instance value goes here",
"__module__": "json_myobj", "__class__": "MyObj"}]
'''
myobj_instance = json.loads(
encoded_object,
object_hook=dict_to_object,
)
print(myobj_instance)
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
由于json将串值转换为Unicode对象,因此,在其被用作类构造函数的关键字参数之前,需要将它们重新编码为ASCII串。
内置类型也有类似的hook,如整数(parse_int)、浮点数(parse_float)和常量(parse_constant)。
1.5 编码器和解码器类
除了之前介绍的便利函数,json模块还提供了一些类来完成编码和解码。直接使用这些类可以访问另外的API来定制其行为。
JSONEncoder使用一个iterable接口生成编码数据“块”,从而更容易将其写至文件或网络套接字,而不必在内存中表示完整的数据结构。
import json
encoder = json.JSONEncoder()
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
for part in encoder.iterencode(data):
print('PART:', part)
- 1
- 2
- 3
- 4
- 5
输出按逻辑单元输出,而不是根据某个大小值。
encode()方法基本上等价于’’.join(encoder.iterencode()),只不过之前会做一些额外的错误检查。
要对任意的对象编码,需要用一个实现覆盖default()方法,这个实现类似于convert_to _builtin_type()中的实现。
import json
class MyObj:
def __init__(self, s):
self.s = s
def __repr__(self):
return '<MyObj({})>'.format(self.s)
class MyEncoder(json.JSONEncoder):
def default(self, obj):
print('default(', repr(obj), ')')
# Convert objects to a dictionary of their representation
d = {
'__class__': obj.__class__.__name__,
'__module__': obj.__module__,
}
d.update(obj.__dict__)
return d
obj = MyObj('internal data')
print(obj)
print(MyEncoder().encode(obj))
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
输出与前一个实现的输出相同。
这里要解码文本,然后将字典转换为一个对象,与前面的实现相比,这需要多做一些工作,不过不算太多。
import json
class MyDecoder(json.JSONDecoder):
def __init__(self):
json.JSONDecoder.__init__(
self,
object_hook=self.dict_to_object,
)
def dict_to_object(self, d):
if '__class__' in d:
class_name = d.pop('__class__')
module_name = d.pop('__module__')
module = __import__(module_name)
print('MODULE:', module.__name__)
class_ = getattr(module, class_name)
print('CLASS:', class_)
args = {
key: value
for key, value in d.items()
}
print('INSTANCE ARGS:', args)
inst = class_(**args)
else:
inst = d
return inst
encoded_object = '''
[{"s": "instance value goes here",
"__module__": "json_myobj", "__class__": "MyObj"}]
'''
myobj_instance = MyDecoder().decode(encoded_object)
print(myobj_instance)
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
输出与前面的例子相同。
1.6 处理流和文件
目前为止,所有例子都假设整个数据结构的编码版本可以一次完全放在内存中。对于很大的数据结构,更合适的做法可能是将编码直接写至一个类似文件的对象。便利函数load()和dump()会接收一个类似文件对象的引用用于读写。
import io
import json
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
f = io.StringIO()
json.dump(data, f)
print(f.getvalue())
- 1
- 2
- 3
- 4
- 5
- 6
类似于这个例子中使用的StringIO缓冲区,也可以使用套接字或常规的文件句柄。
尽管没有优化,即一次只读取数据的一部分,但load()函数还提供了一个好处,它封装了从流输入生成对象的逻辑。
import io
import json
f = io.StringIO('[{"a": "A", "c": 3.0, "b": [2, 4]}]')
print(json.load(f))
- 1
- 2
- 3
- 4
类似于dump(),任何类似文件对象都可以被传递到load()。
1.7 混合数据流
JS0NDecoder包含一个raw_decode()方法,如果一个数据结构后面跟有更多数据,如带尾部文本的JSON数据,则可以用这个方python基础教程法完成解码。返回值是对输入数据解码创建的对象,以及该数据的一个索引(指示在哪里结束解码)。
import json
decoder = json.JSONDecoder()
def get_decoded_and_remainder(input_data):
obj, end = decoder.raw_decode(input_data)
remaining = input_data[end:]
return (obj, end, remaining)
encoded_object = '[{"a": "A", "c": 3.0, "b": [2, 4]}]'
extra_text = 'This text is not JSON.'
print('JSON first:')
data = ' '.join([encoded_object, extra_text])
obj, end, remaining = get_decoded_and_remainder(data)
print('Object :', obj)
print('End of parsed input :', end)
print('Remaining text :', repr(remaining))
print()
print('JSON embedded:')
try:
data = ' '.join([extra_text, encoded_object, extra_text])
obj, end, remaining = get_decoded_and_remainder(data)
except ValueError as err:
print('ERROR:', err)
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
遗憾的是,这种做法只适用于对象出现在输入起始位置的情况。
1.8 命令行上处理JSON
json.tool模块实现了一个命令行程序来重新格式化JSON数据,使数据更易读。
[{"a": "A", "c": 3.0, "b": [2, 4]}]
- 1
输入文件example.json包含一个映射,其中键采用字母表顺序。第一个例子显示了按顺序重新格式化的数据,第二个例子使用了–sort-keys在打印输出之前先对映射键排序。
[
{
"a": "A",
"c": 3.0,
"b": [
2,
4
]
}
]
- 8
- 9
- 10
[
{
"a": "A",
"b": [
2,
4
],
"c": 3.0
}
]
- 8
- 9
- 10