You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为嵌套的dataclass实现自定义JSON序列化功能

问题描述

我正在为dataclass开发自定义JSON编码器,但需要序列化的dataclass嵌套在其他类内部,直接序列化顶层类时无法对嵌套类触发自定义转换逻辑。我的类定义如下:

@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.default仅在序列化顶层Bar类时被调用一次,FooJsonEncoder.custom收到的是拆分后的单个Foo实例转换后的键值对列表,最终输出的JSON中嵌套的Foo实例没有包含我需要额外添加的a_property属性,自定义转换逻辑对嵌套类不生效。请问嵌套类场景下如何正确配置自定义JSON序列化规则?


解决方案

问题原因

当前代码的核心问题是dataclasses.asdict的执行逻辑是先递归将所有嵌套的dataclass实例转换为键值对元组列表,再统一传入你指定的dict_factory。这就导致你的custom方法收到的参数永远是已经拆解后的键值对列表,不会收到原始的Foo实例,自然无法触发你对Foo的自定义处理逻辑。

修复方案

不要依赖asdict做自动嵌套转换,而是手动遍历dataclass字段返回原始值,让JSONEncoder的default方法可以递归处理所有层级的dataclass实例。修改后的编码器代码如下:

class FooJsonEncoder(JSONEncoder):
    def default(self, o):
        # 处理Foo类型,添加自定义属性
        if isinstance(o, Foo):
            return {
                "foo_member": o.foo_member,
                "a_property": o.a_property
            }
        # 处理其他通用dataclass,返回字段原始值,不提前转成普通dict
        elif is_dataclass(o):
            return {field.name: getattr(o, field.name) for field in o.__dataclass_fields__.values()}
        # 其他类型走默认序列化逻辑
        return super().default(o)

修改后运行代码,输出结果符合预期:

{"foo_list": [{"foo_member": 1, "a_property": 2}, {"foo_member": 2, "a_property": 3}]}

扩展说明

如果需要支持更复杂的嵌套场景(比如dict、tuple中嵌套dataclass),可以在default方法中增加对应容器类型的递归处理逻辑,确保所有层级的dataclass实例都能被正确识别处理。


内容的提问来源于stack exchange,提问作者Leonardo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 22:54:03