Python 与 JSON 共舞

简介

在 Python 开发中,尤其是在涉及 web 开发时,不可避免会与 JSON (JavaScript Object Notation) 打交道。本文主要尝试介绍如何在 Python 中使用 JSON 。

在 Python 3 的官方文档中关于 JSON 操作的函数主要有 4 个,它们分别是 json.dump()json.load()json.dumps()json.loads()json.dump()json.dumps() 的功能是将 Python 对象进行编码( encoder ) ,转化为 JSON 格式;而 json.load()json.loads() 则反之,对 JSON 格式对象解码( decoder ),转化为 Python 对象。

json.dump() 与 json.dumps() 的区别

json.dump()json.dumps() 的作用都是把 Python 对象序列化为 JSON 格式,不同之处在哪里呢?先来看一下两者的定义:

1
2
json.dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)
json.dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

两者的定义基本相同,但是 json.dump() 多了一个 fp 参数。它们的主要区别是: json.dumps() 把 Python 对象序列化为一个 JSON 格式的字符串,而 json.dump() 则是把 Python 对象序列化为一个 JSON 格式的流,这个流可以直接写入到文件或者类似文件的对象。

听起来可能有一些拗口,下面来看个例子:

1
2
3
4
5
6
>>> import json
>>> person = {"name":"dormouse", "age":40}
>>> json.dumps(person)
'{"name": "dormouse", "age": 40}'
>>> type(json.dumps(person))
<class 'str'>

从上例中可以看出, json.dumps() 返回了一个 JSON 格式的字符串。

1
2
3
>>> from pathlib import Path
>>> with Path("/tmp/person.txt").open('w') as fp:
... json.dump(person, fp)

上例中的代码生成一个名为 /tmp/person.txt 的文件,其内容为: {"name": "dormouse", "age": 40}

json.load()json.loads() 的区别与之类似。

编码

把 Python 对象序列化为 JSON 格式对象称为编码,主要使用 json.dump()json.dumps() 函数。根据前文所述,这两个函数基本相同,下面主要以 json.dumps() 函数为例,json.dump() 函数的用法基本类似。

json.dumps() 用法示例:

1
2
3
4
>>> import json
>>> person = {"name":"dormouse", "age":40, "blog":["blog1", "其他"]}
>>> json.dumps(person)
'{"name": "dormouse", "age": 40, "blog": ["blog1", "\\u5176\\u4ed6"]}'

编码数据类型对应规则如下表:

Python JSON
dict object
list, tuple array
str string
int, float, int- & float-derived Enums number
True true
False false
None null

常用参数

json.dumps()json.dump() 可以使用许多参数,下面介绍几个常用的参数:

ensure_ascii 参数

该参数如默认值是 True ,在编码时,非 ASCII 字符会被转义,例如在上文的例子中,“其他”两个字被编码为 \\u5176\\u4ed6 。如果该参数设置为 Fasle
那么就不会转义。例如:

1
2
>>> json.dumps(person, ensure_ascii=False)
'{"name": "dormouse", "age": 40, "blog": ["blog1", "其他"]}'

indent 参数

该参数控制编码结果的缩进,默认值是 None 。默认情况下会编码结果会紧缩在一起。如果该参数设置为一个正整数或者 \t ,那么会使编码结果具有更好的可读性。
例如:

1
2
3
4
5
6
7
8
9
>>> print(json.dumps(person, ensure_ascii=False, indent=4))
{
"name": "dormouse",
"age": 40,
"blog": [
"blog1",
"其他"
]
}

sort_keys 参数

该参数控制编码结果是否按键值排序,默认值是 False 。如果该参数设置为 Ture ,那么编码结果的字典的键值会进行排序。示例:

1
2
3
4
5
6
7
8
9
>>> print(json.dumps(person, ensure_ascii=False, indent=4, sort_keys=True))
{
"age": 40,
"blog": [
"blog1",
"其他"
],
"name": "dormouse"
}

更多参数请参阅:Python 3 的官方文档

解码

把 JSON 格式对象转换为 Python 对象称为解码,主要使用 json.load()json.loads() 函数。这两个函数基本相同,区别类似于 json.dump()json.dumps() 函数。下面主要以 json.loads() 函数为例,json.load() 函数的用法基本类似。

json.loads() 用法示例:

1
2
>>> json.loads(json.dumps(person))
{'name': 'dormouse', 'age': 40, 'blog': ['blog1', '其他']}

解码数据类型对应规则如下表:

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
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return "x:{}, y:{}".format(self.x, self.y)

当我们要对其实例进行编码时会产生类似如下异常(文件路径有所省略):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> person = {
... "name":"dormouse",
... "point": Point(1,2)
}
>>> json.dumps(person)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File ".../lib/python3.6/json/__init__.py", line 231, in dumps
return _default_encoder.encode(obj)
File ".../lib/python3.6/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File ".../lib/python3.6/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File ".../lib/python3.6/json/encoder.py", line 180, in default
o.__class__.__name__)
TypeError: Object of type 'Point' is not JSON serializable

产生异常的原因是 JSON 默认的方法无法对这种 Python 对象进行编码。那么如何解决这个问题呢?主要有两种方法:

使用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def my_default(o):
""" 自定义编码函数 """
if isinstance(o, Point):
return (o.x, o.y)
def my_hook(t):
""" 自定义解码函数 """
for k in t:
if k == 'point':
x, y = t[k]
t[k] = Point(x, y)
return t

# 编码
encode_str = json.dumps(person, default=my_default)
print(encode_str)
# 解码
decode_obj = json.loads(encode_str, object_hook=my_hook)
print(decode_obj)

输出结果如下:

1
2
{"name": "dormouse", "point": [1, 2]}
{'name': 'dormouse', 'point': x:1, y:2}

使用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Point):
return (o.x, o.y)
return json.JSONEncoder.default(o)
encode_str = json.dumps(person, cls=MyEncoder)
print(encode_str)

class MyDecoder(json.JSONDecoder):
def decode(self, s):
t = super().decode(s);
for k in t:
if k == 'point':
x, y = t[k]
t[k] = Point(x, y)
return t
decode_obj = json.loads(encode_str, cls=MyDecoder)
print(decode_obj)

输出结果如下:

1
2
{"name": "dormouse", "point": [1, 2]}
{'name': 'dormouse', 'point': x:1, y:2}

参考资料

  1. Python 3 的官方文档

注:

本文的 Python 环境为:

Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 13:51:32)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux