嵌套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
相关产品推荐
相关产品推荐

