0%

Python将对象序列化成JSON格式

前言

在 Python 中经常会做的一件事就是将某个对象序列化, 序列化有很多种方式, JSON 是最常用的其中一种(方便和前端交换数据).

但是一个明显的问题是, Python 标准库json, 仅仅能dumps其内置的基本数据类型, 例如 string, integer, boolean, list, dict …

并且 JSON 作为一种数据交换格式, 有其固定的数据类型.
有时候我们需要将我们编写的一个类序列化, 此时该怎么办?

JSON 的介绍可以参考其官网说明

序列化非标准类型时遇到的问题

如果 json.dumps 一个非标准类型(例如一个我们编写的类)会发生什么事?

我们先定义一个类Person

1
2
3
4
class Person:

def __init__(self):
self.name = "naonao"

尝试用 json 序列化

1
2
3
4
5
6
7
import json

# 创建一个实例
p = Person()

# 尝试序列化
json.dumps(p)

毫不意外的报错了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [76], in <module>
4 p = Person()
6 # 尝试序列化
----> 7 json.dumps(p)

File /opt/conda/lib/python3.9/json/__init__.py:231, in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
226 # cached encoder
227 if (not skipkeys and ensure_ascii and
228 check_circular and allow_nan and
229 cls is None and indent is None and separators is None and
230 default is None and not sort_keys and not kw):
--> 231 return _default_encoder.encode(obj)
232 if cls is None:
233 cls = JSONEncoder

File /opt/conda/lib/python3.9/json/encoder.py:199, in JSONEncoder.encode(self, o)
195 return encode_basestring(o)
196 # This doesn't pass the iterator directly to ''.join() because the
197 # exceptions aren't as detailed. The list call should be roughly
198 # equivalent to the PySequence_Fast that ''.join() would do.
--> 199 chunks = self.iterencode(o, _one_shot=True)
200 if not isinstance(chunks, (list, tuple)):
201 chunks = list(chunks)

File /opt/conda/lib/python3.9/json/encoder.py:257, in JSONEncoder.iterencode(self, o, _one_shot)
252 else:
253 _iterencode = _make_iterencode(
254 markers, self.default, _encoder, self.indent, floatstr,
255 self.key_separator, self.item_separator, self.sort_keys,
256 self.skipkeys, _one_shot)
--> 257 return _iterencode(o, 0)

File /opt/conda/lib/python3.9/json/encoder.py:179, in JSONEncoder.default(self, o)
160 def default(self, o):
161 """Implement this method in a subclass such that it returns
162 a serializable object for ``o``, or calls the base implementation
163 (to raise a ``TypeError``).
(...)
177
178 """
--> 179 raise TypeError(f'Object of type {o.__class__.__name__} '
180 f'is not JSON serializable')

TypeError: Object of type Person is not JSON serializable

我们仔细观察报错信息, 提示 Person 不是一个 JSON 序列化对象

1
TypeError: Object of type Person is not JSON serializable

那么问题来了, 我们如何把各种各样的 Python 对象序列化成 JSON 格式?

Google 和查阅官方文档后你会发现 dumps 方法提供了一个 cls 参数, 我们可以自己编写一个序列化类, 告诉该他该如何dumps这个对象.

解决方案

例如

1
2
3
4
5
6
7
8
9
class PersonEncoder(json.JSONEncoder):

# 重写该方法, 告诉 json
# 如何 dumps 指定的对象
def default(self, _object):
if isinstance(_object, Person):
# 这里编写如何序列化 Person
# 对象的代码
return dict(name=_object.name)

我们再尝试一下

1
json.dumps(p, cls=PersonEncoder)

得到结果

1
'{"name": "naonao"}'

成功了, 完美的解决了问题.

有点遐思

但是现在有一个问题, 如果序列化少量类, 我们只需要在default这个方法下编写少量的代码即可. 但通常一个应用不可能仅仅只有几个少量的类, 类多了怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def default(self, _object):
if isinstance(_object, Person):
...
elif isinstance(_object, Person1):
...
elif isinstance(_object, Person2):
...
elif isinstance(_object, Person3):
...
elif isinstance(_object, Person4):
...
elif isinstance(_object, Person5):
...
elif isinstance(_object, Person6):
...
elif isinstance(_object, Person7):
...

这种写法, 一点也不优雅!

我们需要一种更优雅的解决方案

更优雅的解决方案

该函数可以解决这个问题

1
from functools import singledispatch

详细说明可以看官方文档

我们先创建两个类

1
2
3
4
5
6
7
8
9
10
11
12
13
import json
from functools import singledispatch

class Person:

def __init__(self):
self.name = "naonao"
self.age = 18

class Animal:

def __init__(self):
self.name = "dog"

创建两个实例

1
2
person = Person()
animal = Animal()

接下来我们可以这样定义序列化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@singledispatch
def serialize(_object):
return json.dumps(_object)

@serialize.register(Person)
def _(_object):
return dict(name=_object.name,
age=_object.age)

@serialize.register(Animal)
def _(_object):
return dict(name=_object.name)

class CustomJSONEncoder(json.JSONEncoder):

def default(self, _object):
return serialize(_object)

尝试一下

1
2
3
4
5
data = {
"person": person,
"animal": animal
}
json.dumps(data, cls=CustomJSONEncoder)

完美解决

1
'{"person": {"name": "naonao", "age": 18}, "animal": {"name": "dog"}}'

接下来有新的类(或是数据类型)进行序列化时, 我们仅仅需要参照这个格式

1
2
3
4
5
6
                    # 目标类
@serialize.register(Person)
def _(_object):
# 如何序列化这个对象
return dict(name=_object.name,
age=_object.age)

编写对应的解析器即可. 这样可比 if else ...循环嵌套可读性高了不知多少倍!

参考链接

https://juejin.cn/post/6844903510543171592

https://docs.python.org/zh-cn/3/library/functools.html

http://www.json.org/json-zh.html