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

嵌套Pydantic模型JSON导出加载保留子类类型的实现方法

Pydantic多态序列化保留类型信息解决方案

你遇到的是Pydantic多态场景下的典型问题:字段注解为父类类型、实际存储子类实例时,默认序列化不会携带类型标识,反序列化时会按父类规则校验,导致子类新增字段被extra='forbid'拦截。以下是两种完全保留Pydantic原生能力的解决方案:


方案1:内置鉴别器实现(最推荐,无自定义逻辑)

利用Pydantic原生的多态鉴别器能力,仅需要调整模型定义,原有序列化/反序列化代码完全不需要修改:

import pydantic
from typing import Literal, Union

class Base(pydantic.BaseModel):
    class Config:
        extra = 'forbid'

# 父类新增type字段作为类型鉴别标记
class Thing(Base):
    thing_id: int
    type: Literal["Thing"] = "Thing"

# 子类重写type字段的固定值
class SubThing(Thing):
    type: Literal["SubThing"] = "SubThing"
    name: str

# 容器类字段注解改为Union,指定鉴别器字段
class Container(Base):
    thing: Union[Thing, SubThing] = pydantic.Field(discriminator='type')

序列化后JSON会自动携带type字段:

{
  "thing": {
    "thing_id": 1,
    "name": "my_thing",
    "type": "SubThing"
  }
}

直接用Container.parse_raw(json_string)即可自动识别子类类型完成反序列化,不会触发字段校验报错。

如果子类数量多不想手动维护Union,可以给父类注册自动校验逻辑,新增子类无需修改其他代码:

class Thing(Base):
    thing_id: int
    type: str = "Thing"
    # 自动注册所有子类
    _subclass_map = {}
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls._subclass_map[cls.__name__] = cls

    @classmethod
    def __get_validators__(cls):
        yield cls._validate_subtype

    @classmethod
    def _validate_subtype(cls, value):
        if isinstance(value, dict) and 'type' in value:
            subclass = cls._subclass_map.get(value['type'])
            if subclass:
                return subclass(**value)
        return cls(**value)

# 后续新增子类无需修改其他代码
class SubThing(Thing):
    type: str = "SubThing"
    name: str

class Container(Base):
    thing: Thing # 注解还是父类,不需要改Union

方案2:基类自定义序列化逻辑(无侵入适配现有模型)

如果不想修改现有模型的字段定义,可以直接在Base基类中重写序列化/反序列化方法,自动追加类型标记:

import pydantic
import json
from typing import Any

class Base(pydantic.BaseModel):
    class Config:
        extra = 'forbid'

    def dict(self, **kwargs) -> dict[str, Any]:
        # 序列化时自动追加类名标记
        data = super().dict(**kwargs)
        data["__class__"] = self.__class__.__name__
        return data

    @classmethod
    def parse_raw(cls, data: str | bytes, **kwargs) -> "Base":
        parsed = json.loads(data)
        # 反序列化时优先匹配子类
        if "__class__" in parsed:
            for subclass in cls.__subclasses__():
                if subclass.__name__ == parsed["__class__"]:
                    return subclass(**parsed)
        return super().parse_raw(data,**kwargs)

这个方案不需要修改任何现有业务模型定义,原有代码的json()、parse_raw()调用完全兼容。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 05:24:03