Python学习:python对象转换json字符串

1.问题

json是网络传输比较简单易用。python中,jsondict对象可以相互转换,首先我们看下简单的dict对象转换。

student = {
    'name': 'chaos',
    'age': 18,
    'school': {
        'name': "tsinghua"
    }
}

print(json.dumps(student))

输出为:

{"name": "chaos", "age": 18, "school": {"name": "tsinghua"}}

一般情况下,我们遇到的类型一般并不是dict,而是class。我们的问题是pythonclass如何转换成json呢?虽然pythonclassdict比较像,转化成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()
    
    # 直接转化json节点的类型
    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节点。比如intstr。默认情况下,并没有包换复数类型Complex
  • _collect_type类型参数,表示转换为json的数组节点,比如listtuple等等。

测试代码如下:

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 = (, , , , )
_collect_type = (, , )

    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)

进一步思考的话,可能也会有解决办法,但是我们需要的是解决当前的问题,不一定需要解决所有情况下的问题。如果实现过于复杂的话,不妨到此为止,只要能解决当前的问题就行,将来遇到再说也不迟。

你可能感兴趣的:(Python学习:python对象转换json字符串)