1.问题
json 是网络传输比较简单易用。python 中,json 和dict 对象可以相互转换,首先我们看下简单的dict对象转换。
student = {
'name': 'chaos',
'age': 18,
'school': {
'name': "tsinghua"
}
}
print(json.dumps(student))
输出为:
{"name": "chaos", "age": 18, "school": {"name": "tsinghua"}}
一般情况下,我们遇到的类型一般并不是dict ,而是class 。我们的问题是python 的class 如何转换成json 呢?虽然python 中class 与dict 比较像,转化成dict 是比较麻烦的过程。本文我们就看看如何把python 对象转换为dict 。
2.方案
首先我们直接使用@dataclass 进行类型的定义,然后再看普通的class 类型。我们先定义类型:
@dataclass
class School:
name: str
@dataclass
class Student:
name: str
age: int
school: School
class 有个属性__dict__ ,该属性包换了class 的属性成员。最初的版本转换版本代码如下:
def obj2json(obj, atom_type: list = None, collect_type: list = None) -> str:
def _obj2dict(in_obj, dc: dict, _atom_type, _collect_type):
for key, value in in_obj.__dict__.items():
if value is None:
dc[key] = None
elif isinstance(value, _atom_type):
dc[key] = value
elif isinstance(value, _collect_type):
dc[key] = list()
for item in value:
sub_dc = dict()
_obj2dict(item, sub_dc, _atom_type, _collect_type)
dc[key].append(sub_dc)
else:
dc[key] = dict()
_obj2dict(value, dc[key], _atom_type, _collect_type)
ret = dict()
if not atom_type:
_atom_type = (int, float, str, bool, bytes)
else:
_atom_type = tuple(set(atom_type + [int, float, str, bool, bytes]))
if not collect_type:
_collect_type = (set, tuple, list)
else:
_collect_type = tuple(set(collect_type + [set, tuple, list]))
_obj2dict(obj, ret, _atom_type, _collect_type)
return json.dumps(ret)
_atom_type 类型参数,表示直接转换为json 节点。比如int 、str 。默认情况下,并没有包换复数类型Complex 。_collect_type 类型参数,表示转换为json 的数组节点,比如list 、tuple 等等。
测试代码如下:
student = Student(name='chaos', age=18, school=School('tsinghua'))
ret = obj2json0(student)
print(ret)
输出:
{"name": "chaos", "age": 18, "school": {"name": "tsinghua"}}
这一版本其实看上去不错。不过,遇到下面这个情况时,转换就出了问题。
@dataclass
class School:
name: str
@dataclass
class Student:
name: str
age: int
school: School
contact: dict
def test_dataclass_002():
ret = json.dumps(student)
assert ret == {"name": "chaos", "age": 18, "school": {"name": "tsinghua"}}
def test_dataclass_003():
student = Student(name='chaos', age=18, school=School('tsinghua'), contact=dict(number='13988889999'))
ret = obj2json0(student)
print(ret)
运行时会抛出异常:
in_obj = {'number': '13988889999'}, dc = {}
_atom_type = (<class 'int'>, <class 'float'>, <class 'str'>, <class 'bool'>, <class 'bytes'>)
_collect_type = (<class 'set'>, <class 'tuple'>, <class 'list'>)
def _obj2dict(in_obj, dc: dict, _atom_type, _collect_type):
> for key, value in in_obj.__dict__.items():
E AttributeError: 'dict' object has no attribute '__dict__'
问题是对于内置类型dict 的对象,没有__dict__ 属性。这也很好解决,先判定一下是否dict 类型,代码如下:
def obj2json(obj, atom_type: list = None, collect_type: list = None) -> str:
def _obj2dict(in_obj, dc: dict, _atom_type, _collect_type):
if isinstance(in_obj, dict):
dict_obj = in_obj
else:
dict_obj = in_obj.__dict__
for key, value in dict_obj.items():
if value is None:
dc[key] = None
elif isinstance(value, _atom_type):
dc[key] = value
elif isinstance(value, dict):
dc[key] = dict()
_obj2dict(value, dc[key], _atom_type, _collect_type)
elif isinstance(value, _collect_type):
dc[key] = list()
for item in value:
sub_dc = dict()
_obj2dict(item, sub_dc, _atom_type, _collect_type)
dc[key].append(sub_dc)
else:
dc[key] = dict()
_obj2dict(value, dc[key], _atom_type, _collect_type)
ret = dict()
if not atom_type:
_atom_type = (int, float, str, bool, bytes)
else:
_atom_type = tuple(set(atom_type + [int, float, str, bool, bytes]))
if not collect_type:
_collect_type = (set, tuple, list)
else:
_collect_type = tuple(set(collect_type + [set, tuple, list]))
_obj2dict(obj, ret, _atom_type, _collect_type)
return json.dumps(ret)
我们现在看看上述的转换,能否适用于普通的class 。代码如下:
class School:
def __init__(self, name):
self.name = name
class Student:
def __init__(self, name, age, contact, school):
self.name = name
self.age = age
self.school = school
self.contact = contact
def get_name(self):
return self.name
@classmethod
def get_class_name(cls):
return "Student"
student = Student(name='chaos', age=18, contact=dict(number='13999998888'), school=School('tsinghua'))
ret = obj2json(student)
print(ret)
输出如下:
{"name": "chaos", "age": 18, "school": {"name": "tsinghua"}, "contact": {"number": "13999998888"}}
看上去很好,新定义的对象函数或者类函数,都没有影响到结果输出。
3.讨论
到目前为止,转换函数看上去不错。不过,并不完美,此问题比看上去复杂。比如,对于双向链表这样的函数,就没有办法处理,会出现循环引用。比如下面这个代码:
class TreeNode:
def __init__(self, value, pre_node=None, next_node=None):
self.value = value
self.pre_node = pre_node
self.next_node = next_node
root = TreeNode(10)
next_root = TreeNode(11)
root.pre_node = next_root
root.next_node = next_root
next_root.pre_node = root
next_root.next_node = root
obj2json(root)
进一步思考的话,可能也会有解决办法,但是我们需要的是解决当前的问题,不一定需要解决所有情况下的问题。如果实现过于复杂的话,不妨到此为止,只要能解决当前的问题就行,将来遇到再说也不迟。
|