Python Json序列化

记录一下Python对象中关于Json的序列化和反序列化。

Python中常用的Json序列化采用json.dupms以及json.loads来实现。

json.dumps

json.dumps是我们最为常用的序列化方式,用于dict转换成Json字符串

为了解决对象序列化的问题,一般需要利用__dict__来获取对象的所有字段内容。

1
json.dumps(obj, default=lambda obj: obj.__dict__)

存在问题:如果某一字段的类型为某一个类,在序列过程中就会出现问题。

此时,由于该非基础类型的字段无法直接转化为dict,从而lambda obj: obj.__dict__)会失效。

为了解决上述问题,一般可以通过定制化每一个类的序列化函数,然后通过default来指定。

类似这种,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student:
def __init__(self):
self.name = None
self.age = None
# Address
self.address = None

class Address:
def __init__(self):
self.city = None
self.province = None
self.area_code = None
self.area_detail = None

def student2d(student_obj):
...

json.dumps(obj, default=student2d)
json.loads

json.loads是我们最为常用的反序列化方式,用于Json字符串转换成dict

1
json.loads(json_str, object_hook=xxxx)

存在同样的问题:如果某一字段的类型为某一个类,在反序列过程中时无法识别到具体的类结构。

存在上述问题的原因在于,Python属于非强类型检查,初始化的类时无法获取字段的具体类型,只有在运行期初始化才确定。

同样,为了解决上述问题,一般可以通过定制化每一个类的反序列化函数,然后通过object_hook来指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student:
def __init__(self):
self.name = None
self.age = None
# Address
self.address = None

class Address:
def __init__(self):
self.city = None
self.province = None
self.area_code = None
self.area_detail = None

def d2student(self, d):
...

json.loads(obj, object_hook=d2student)
通用序列化

仍然存在的问题:每个类都需要定义自己的序列化和反序列化函数

上面的解决方式会带来大量的重复编码的功能。

为了解决这个问题,利用abc模块实现序列化的抽象,如下

1
2
3
4
import abc

class AbstractJsonSerializer(abc.ABC):
pass

所有带有Json序列化功能的类都需要继AbstractJsonSerializer

下面简单介绍下序列化和反序列化的思路。

serialize

序列化过程中,为了避免__dict__中特殊类造成的影响,需要对字典值类型做判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 循环遍历对象字段
for k, v in self.__dict__.items():
# 字段值属于基础类型
if isinstance(v, int) or isinstance(v, str) \
or isinstance(v, float) or isinstance(v, list) \
or isinstance(v, tuple) or isinstance(v, set) \
or isinstance(v, dict):
_d[k] = v
# 字段值时特殊类型
else:
if v is None:
continue
# 判断字段值是否实现了AbstractJsonSerializer
if not issubclass(v.__class__, AbstractJsonSerializer):
raise Exception("[%s] must be implemented JsonSerializer" % v.__class__)
# 由于特殊类型字段值实现了AbstractJsonSerializer,则直接调用序列化赋值
_d[k] = v.serialize()
deserialize

反序列化过程中,为了避免特殊类造成的类型无法确定的问题,需要在类中指定特殊字段类型的映射。

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
# 抽象方法,指定特殊字段类型映射
@abc.abstractmethod
def field_cls(self):
# 返回值例如,return {'address': Address}
pass

def deserialize(self, d: dict):
# 获取特殊字段类型映射
cls_map = self.field_cls()
# 判断特殊字段类型映射是否满足要求
if cls_map is not None and not isinstance(cls_map, dict):
raise Exception('[%s].special_field_class_map is must be a dict' % self.__class__)
# 遍历类对象的字段
for k in self.__dict__.keys():
if k not in d:
continue
# 非特殊字段类型
if not cls_map or k not in cls_map:
self.__setattr__(k, d[k])
# 特殊字段类型
else:
cls = cls_map[k]
# 判断特殊字段类型是否实现了序列化
if not issubclass(cls, AbstractJsonSerializer):
raise Exception("[%s] must be implemented JsonSerializer" % cls.__class__)
# 由于特殊类型字段值实现了AbstractJsonSerializer,则直接调用反序列化赋值
self.__setattr__(k, cls().deserialize(d[k]))
return self
驼峰和下划线命名方式的转换

下面两个方法时用来兼容驼峰下划线的字段命名的不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 字段去除`_`并全部小写
@staticmethod
def format_attr(key):
return key.replace('_', '').lower()

# 代替__setattr__
@staticmethod
def set_attr(d, obj):
# 存储对象字段映射,{格式化字段:字段}
obj_key_dict = dict()
for key in obj.__dict__.keys():
if key.startswith('__'):
continue
obj_key_dict[AbstractJsonSerializer.format_attr(key)] = key
# 遍历字典中数据
for key in d.keys():
# 获取字典的格式化key
format_key = AbstractJsonSerializer.format_attr(key)
# 判断是否存在于对象字段映射
if format_key in obj_key_dict:
obj.__setattr__(obj_key_dict[format_key], d[key])
完整版实现
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# -*- coding: utf-8 -*-

import abc


class AbstractJsonSerializer(abc.ABC):
"""
Json Serializer
"""
@abc.abstractmethod
def field_cls(self):
"""
return field class map
:return:
"""
pass

def serialize(self, need_format=False):
"""
convert obj to dict
:return: dict
"""
_d = dict()
d = self.__dict__
for k, v in d.items():
if isinstance(v, int) or isinstance(v, str) \
or isinstance(v, float) or isinstance(v, list) \
or isinstance(v, tuple) or isinstance(v, set) \
or isinstance(v, dict):
_d[k] = v
else:
if v is None:
continue
if not issubclass(v.__class__, AbstractJsonSerializer):
raise Exception("[%s] must be implemented JsonSerializer" % v.__class__)
_d[k] = v.serialize()
return _d

def deserialize(self, d: dict):
"""
convert dict to obj
:param: d: dict
:return: obj
"""
cls_map = self.field_cls()
if cls_map is not None and not isinstance(cls_map, dict):
raise Exception('[%s].special_field_class_map is must be a dict' % self.__class__)
for k in self.__dict__.keys():
if k not in d:
continue
if not cls_map or k not in cls_map:
self.__setattr__(k, d[k])
else:
cls = cls_map[k]
if not issubclass(cls, AbstractJsonSerializer):
raise Exception("[%s] must be implemented JsonSerializer" % cls.__class__)
self.__setattr__(k, cls().deserialize(d[k]))
return self

@staticmethod
def format_attr(key):
"""
format field for x_y or xY to xy
:param key: field
:return: format field
"""
return key.replace('_', '').lower()

@staticmethod
def set_attr(d, obj):
"""

:param d: dict of obj
:param obj: target obj
:return: None
"""
obj_key_dict = dict()
for key in obj.__dict__.keys():
if key.startswith('__'):
continue
obj_key_dict[AbstractJsonSerializer.format_attr(key)] = key
for key in d.keys():
format_key = AbstractJsonSerializer.format_attr(key)
if format_key in obj_key_dict:
obj.__setattr__(obj_key_dict[format_key], d[key])


class Student(AbstractJsonSerializer):
def field_cls(self):
return {'address': Address}

def __init__(self):
self.name = None
self.age = None
self.address = None


class Address(AbstractJsonSerializer):
def field_cls(self):
return None

def __init__(self):
self.city = None
self.province = None
self.area_code = None
self.area_detail = None


if __name__ == '__main__':
s = Student()
s.name = 'jack'
s.age = 11
a = Address()
a.city = 'hangzhou'
a.province = 'zhejiang'
a.area_code = '31000'
a.area_detail = 'gongshuqu'
s.address = a

d = s.serialize()
print(d)

ss = Student().deserialize(d)
print(ss)