序列化嵌套类

Posted

技术标签:

【中文标题】序列化嵌套类【英文标题】:Serializing nested classes 【发布时间】:2022-01-06 15:20:58 【问题描述】:

我正在尝试为dataclass 创建一个custom JSON encoder,但该类实际上嵌入在另一个类中,并且***类被序列化。我的类定义是这样的:

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class Foo():
    foo_member: int = 1

    @property
    def a_property(self):
        return self.foo_member+1

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class Bar():
    foo_list: List[Foo] = field(default_factory=list)

我的整个测试代码是:

from dataclasses import dataclass, field, asdict, is_dataclass
from typing import List

from json import JSONEncoder

from pprint import pprint

class FooJsonEncoder(JSONEncoder):
    '''
    This should be used exclusively for encoding the ELF metadata as KDataFormat
    is treated differently here.
    '''
    def custom(self, x):
        print(f'custom type type(x)')

        if isinstance(x, list):
            print(f'here dict(x)')
            pprint(x)

        if isinstance(x, Foo):
            d = asdict(x)
            d['a_property'] = getattr(x, 'a_property')
            return d
        elif is_dataclass(x):
            return asdict(x)
        return dict(x)

    def default(self, o):
        print(f'default type type(o)')
        if isinstance(o, Foo):
            d = asdict(o)
            d['a_property'] = getattr(o, 'a_property')
            return d
        elif is_dataclass(o):
            return asdict(o, dict_factory=self.custom)

        return super(FooJsonEncoder, self).default(o)


@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class Foo():
    foo_member: int = 1

    @property
    def a_property(self):
        return self.foo_member+1

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class Bar():
    foo_list: List[Foo] = field(default_factory=list)


def main():
    foo1 = Foo(1)
    foo2 = Foo(2)
    assert 2 == foo1.a_property
    assert 3 == foo2.a_property

    bar = Bar(foo_list=[foo1, foo2])

    print(FooJsonEncoder().encode(bar))

if __name__ == "__main__":
    main()

当我运行它时,我得到了

default type <class '__main__.Bar'>
custom type <class 'list'>
here 'foo_member': 1
[('foo_member', 1)]
custom type <class 'list'>
here 'foo_member': 2
[('foo_member', 2)]
custom type <class 'list'>
here 'foo_list': ['foo_member': 1, 'foo_member': 2]
[('foo_list', ['foo_member': 1, 'foo_member': 2])]
"foo_list": ["foo_member": 1, "foo_member": 2]

我的FooJsonEncoder.defaultmain 调用一次。有趣的是,FooJsonEncoder.custom 是使用拆分列表而不是两个 Foo 对象的列表来调用的:

custom type <class 'list'>
here 'foo_member': 1
[('foo_member', 1)]
custom type <class 'list'>
here 'foo_member': 2
[('foo_member', 2)]

然后被两个成员列表调用,但已经转换为dict

custom type <class 'list'>
here 'foo_list': ['foo_member': 1, 'foo_member': 2]
[('foo_list', ['foo_member': 1, 'foo_member': 2])]
"foo_list": ["foo_member": 1, "foo_member": 2]

一旦return dict(x)custom 中被调用,我就不能对嵌套类使用自定义转换。

类嵌套时如何传递自定义 JSON 序列化器?

谢谢。

【问题讨论】:

你为什么要这样做,例如而不是使用现有的序列化库?看起来这将是一项相当多的工作,并且可能会在您的项目中引入(或促成)代码膨胀。 因为我们需要 JSON 中的类属性。常规序列化库只导出属性,不导出属性。 【参考方案1】:

我认为问题在于 asdict 是递归的,但不能让您访问中间的步骤。因此,一旦您点击 bar asdict 就会接管并序列化所有数据类。可能有办法让a_property 成为一个字段并回避这个问题。

但我只是手动将数据类转换为字典,让我添加额外的字段。你只需要知道 asdict 实际上会复制列表,这并没有这样做,但它不是必需的,因为它都被序列化为一个字符串。

from dataclasses import (
    dataclass,
    field,
    is_dataclass,
    fields)
from typing import List

from json import JSONEncoder
from pprint import pformat

class FooJsonEncoder(JSONEncoder):

    def default(self, obj):
        print ('serializing '.format(pformat(obj)))
        if is_dataclass(obj):
            print ('    is dataclass')
            fieldnames = [f.name for f in fields(obj)]
            if isinstance(obj, Foo):
                print ('    is Foo')
                fieldnames.append('a_property')
            d = dict([(name, getattr(obj, name)) for name in fieldnames])
            print('    serialized to '.format(pformat(d)))
            return d
        else:
            pprint ('    is not dataclass')
            res = super(FooJsonEncoder, self).default(obj)
            print('    serialized to '.format(pformat(res)))
            return res


@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class Foo():
    foo_member: int = 1

    @property
    def a_property(self):
        return self.foo_member+1

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class Bar():
    foo_list: List[Foo] = field(default_factory=list)


def main():
    foo1 = Foo(1)
    foo2 = Foo(2)
    assert 2 == foo1.a_property
    assert 3 == foo2.a_property

    bar = Bar(foo_list=[foo1, foo2])

    print(FooJsonEncoder().encode(bar))

if __name__ == "__main__":
    main()

【讨论】:

以上是关于序列化嵌套类的主要内容,如果未能解决你的问题,请参考以下文章

如何反序列化包含同一类嵌套的json类(Unity C#)?

我可以告诉 WCF WebAPI 序列化程序忽略嵌套类对象吗?

json.net 到 System.text.json 对 .net 5 中嵌套类的期望

如何正确地将 JSON 字符串反序列化为包含另一个类的嵌套列表的类

Django:相互嵌套序列化程序

如何将具有嵌套属性的 JSON 对象反序列化为 Symfony 实体?