简介

在 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 格式对象解码( ecoder ),转化为 Python 对象。

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

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

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 格式的流,这个流可以直接写入到文件或者类似文件的对象。

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

>>> import json
>>> person = {"name":"dormouse", "age":40}
>>> json.dumps(person)
'{"name": "dormouse", "age": 40}'
>>> type(json.dumps(person))
<class 'str'>

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

>>> 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() 用法示例:

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

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

PythonJSON
dictobject
list, tuplearray
strstring
int, float, int- & float-derived Enumsnumber
Truetrue
Falsefalse
Nonenull

常用参数

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

ensure_ascii 参数

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

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

indent 参数

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

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

sort_keys 参数

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

>>> 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() 用法示例:

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

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

JSONPython
objectdict
arraylist
stringstr
number (int)int
number (real)float
trueTrue
falseFalse
nullNone

自定义类型处理

有时候,我们需要处理一些自定义的类型,例如有如下这个类:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return "x:{}, y:{}".format(self.x, self.y)

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

>>> 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 对象进行编码。那么如何解决这个问题呢?主要有两种方法:

使用函数

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)

输出结果如下:

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

使用类

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)

输出结果如下:

{"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