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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容