本节书摘来自异步社区《Python Cookbook(第3版)中文版》一书中的第6章,第6.2节,作者[美]David Beazley , Brian K.Jones,陈舸 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。
6.2 读写JSON数据
6.2.1 问题
我们想读写以JSON(JavaScript Object Notation)格式编码的数据。
6.2.2 解决方案
json模块中提供了一种简单的方法来编码和解码JSON格式的数据。这两个主要的函数就是json.dumps()以及json.loads()。这两个函数在命名上借鉴了其他序列化处理库的接口,比如pickle。下面的示例展示了如何将Python数据结构转换为JSON:
import json
data = {
'name' : 'ACME',
'shares' : 100,
'price' : 542.23
}
json_str = json.dumps(data)
而接下来的示例告诉我们如何把JSON编码的字符串再转换回Python数据结构:
data = json.loads(json_str)
如果要同文件而不是字符串打交道的话,可以选择使用json.dump()以及json.load()来编码和解码JSON数据。示例如下:
# Writing JSON data
with open('data.json', 'w') as f:
json.dump(data, f)
# Reading data back
with open('data.json', 'r') as f:
data = json.load(f)
6.2.3 讨论
JSON编码支持的基本类型有None、bool、int、float和str,当然还有包含了这些基本类型的列表、元组以及字典。对于字典,JSON会假设键(key)是字符串(字典中的任何非字符串键都会在编码时转换为字符串)。要符合JSON规范,应该只对Python列表和字典进行编码。此外,在Web应用中,把最顶层对象定义为字典是一种标准做法。
JSON编码的格式几乎与Python语法一致,只有几个小地方稍有不同。比如,True会被映射为true,False会被映射为false,而None会被映射为null。下面的示例展示了编码看起来是怎样的:
>>> json.dumps(False)
'false'
>>> d = {'a': True,
... 'b': 'Hello',
... 'c': None}
>>> json.dumps(d)
'{"b": "Hello", "c": null, "a": true}'
>>>
如果要检查从JSON中解码得到的数据,那么仅仅将其打印出来就想确定数据的结构通常是比较困难的——尤其是如果数据中包含了深层次的嵌套结构或者有许多字段时。为了帮助解决这个问题,考虑使用pprint模块中的pprint()函数。这么做会把键按照字母顺序排列,并且将字典以更加合理的方式进行输出。下面的示例展示了应该如何对Twitter上的搜索结果以漂亮的格式进行输出:
>>> from urllib.request import urlopen
>>> import json
>>> u = urlopen('http://search.twitter.com/search.json?q=python&rpp=5')
>>> resp = json.loads(u.read().decode('utf-8'))
>>> from pprint import pprint
>>> pprint(resp)
{'completed_in': 0.074,
'max_id': 264043230692245504,
'max_id_str': '264043230692245504',
'next_page': '?page=2&max_id=264043230692245504&q=python&rpp=5',
'page': 1,
'query': 'python',
'refresh_url': '?since_id=264043230692245504&q=python',
'results': [{'created_at': 'Thu, 01 Nov 2012 16:36:26 +0000',
'from_user': ...
},
{'created_at': 'Thu, 01 Nov 2012 16:36:14 +0000',
'from_user': ...
},
{'created_at': 'Thu, 01 Nov 2012 16:36:13 +0000',
'from_user': ...
},
{'created_at': 'Thu, 01 Nov 2012 16:36:07 +0000',
'from_user': ...
}
{'created_at': 'Thu, 01 Nov 2012 16:36:04 +0000',
'from_user': ...
}],
'results_per_page': 5,
'since_id': 0,
'since_id_str': '0'}
>>>
一般来说,JSON解码时会从所提供的数据中创建出字典或者列表。如果想创建其他类型的对象,可以为json.loads()方法提供object_pairs_hook或者object_hook参数。例如,下面的示例展示了我们应该如何将JSON数据解码为OrderedDict(有序字典),这样可以保持数据的顺序不变:
>>> s = '{"name": "ACME", "shares": 50, "price": 490.1}'
>>> from collections import OrderedDict
>>> data = json.loads(s, object_pairs_hook=OrderedDict)
>>> data
OrderedDict([('name', 'ACME'), ('shares', 50), ('price', 490.1)])
>>>
而下面的代码将JSON字典转变为Python对象:
>>> class JSONObject:
... def __init__(self, d):
... self.__dict__ = d
...
>>>
>>> data = json.loads(s, object_hook=JSONObject)
>>> data.name
'ACME'
>>> data.shares
50
>>> data.price
490.1
>>>
在上一个示例中,通过解码JSON数据而创建的字典作为单独的参数传递给了__init__()。之后就可以*地根据需要来使用它了,比如直接将它当做对象的字典实例来用。
有几个选项对于编码JSON来说是很有用的。如果想让输出格式变得漂亮一些,可以在json.dumps()函数中使用indent参数。这会使得数据能够像pprint()函数那样以漂亮的格式打印出来。示例如下:
>>> print(json.dumps(data))
{"price": 542.23, "name": "ACME", "shares": 100}
>>> print(json.dumps(data, indent=4))
{
"price": 542.23,
"name": "ACME",
"shares": 100
}
>>>
如果想在输出中对键进行排序处理,可以使用sort_keys参数:
>>> print(json.dumps(data, sort_keys=True))
{"name": "ACME", "price": 542.23, "shares": 100}
>>>
类实例一般是无法序列化为JSON的。比如说:
>>> class Point:
... def __init__(self, x, y):
... self.x = x
... self.y = y
...
>>> p = Point(2, 3)
>>> json.dumps(p)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.3/json/__init__.py", line 226, in dumps
return _default_encoder.encode(obj)
File "/usr/local/lib/python3.3/json/encoder.py", line 187, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/local/lib/python3.3/json/encoder.py", line 245, in iterencode
return _iterencode(o, 0)
File "/usr/local/lib/python3.3/json/encoder.py", line 169, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <__main__.Point object at 0x1006f2650> is not JSON serializable
>>>
如果想序列化类实例,可以提供一个函数将类实例作为输入并返回一个可以被序列化处理的字典。示例如下:
def serialize_instance(obj):
d = { '__classname__' : type(obj).__name__ }
d.update(vars(obj))
return d
如果想取回一个实例,可以编写这样的代码来处理:
# Dictionary mapping names to known classes
classes = {
'Point' : Point
}
def unserialize_object(d):
clsname = d.pop('__classname__', None)
if clsname:
cls = classes[clsname]
obj = cls.__new__(cls) # Make instance without calling __init__
for key, value in d.items():
setattr(obj, key, value)
return obj
else:
return d
最后给出如何使用这些函数的示例:
>>> p = Point(2,3)
>>> s = json.dumps(p, default=serialize_instance)
>>> s
'{"__classname__": "Point", "y": 3, "x": 2}'
>>> a = json.loads(s, object_hook=unserialize_object)
>>> a
<__main__.Point object at 0x1017577d0>
>>> a.x
2
>>> a.y
3
>>>