
本文介绍使用 @model_serializer(mode='wrap') 在 Pydantic v2 中实现对任意深度嵌套模型自动注入 __name__ 字段的可靠方案,避免手动 exclude 或递归重写 model_dump,确保序列化时类名信息完整、一致且可扩展。
本文介绍使用 `@model_serializer(mode='wrap')` 在 pydantic v2 中实现对任意深度嵌套模型自动注入 `__name__` 字段的可靠方案,避免手动 exclude 或递归重写 `model_dump`,确保序列化时类名信息完整、一致且可扩展。
在 Pydantic v2 中,若希望所有嵌套子模型(无论嵌套多深)在调用 model_dump() 时自动包含其自身类名(如 __name__ 或 class_name),直接依赖 exclude 参数无法递归生效——因为 exclude 仅作用于顶层字段,不会穿透到嵌套模型内部。原问题中尝试通过 exclude={"class_name": True} 期望全局剔除该字段,实际无效;而硬编码修改 model_dump 或滥用 @model_serializer(非 wrap 模式)又易导致无限递归或丢失默认序列化逻辑。
正确解法是采用 @model_serializer(mode='wrap'):它提供一个 handler 函数,用于委托执行 Pydantic 默认的序列化流程,并允许你在其返回结果上安全地添加、修改或过滤字段——且该逻辑会自动递归应用于所有继承该基类的嵌套模型。
以下是推荐实现:
from typing import Any, Dict
from pydantic import BaseModel, SerializerFunctionWrapHandler, SerializationInfo, model_serializer
class NamedBaseModel(BaseModel):
@model_serializer(mode='wrap')
def serialize(
self,
handler: SerializerFunctionWrapHandler,
info: SerializationInfo,
) -> Dict[str, Any]:
# 调用默认序列化(自动递归处理所有嵌套模型)
result = handler(self, info)
# 注入当前模型类名(可替换为 'class_name' 等任意键名)
result['__name__'] = self.__class__.__name__
return result
class FooModel(NamedBaseModel):
val: int
class BarModel(NamedBaseModel):
val: str
foo: FooModel
# 使用示例
obj = BarModel(val='str', foo=FooModel(val=123))
print(obj.model_dump())
# 输出:
# {
# '__name__': 'BarModel',
# 'val': 'str',
# 'foo': {
# '__name__': 'FooModel',
# 'val': 123
# }
# }✅ 关键优势:
-
天然递归支持:
handler(self, info)自动触发子模型的serialize方法,无需手动遍历或递归调用。 - 零侵入默认行为:保留所有字段验证、类型转换、别名映射等 Pydantic 原生能力。
-
灵活定制:可在
result上执行任意操作(如删除敏感字段、添加上下文元数据、动态改名等)。
⚠️ 注意事项:
- 不要在此方法内调用
self.model_dump(),否则将引发无限递归(因model_dump()内部会再次触发该 serializer)。 - 若需条件性注入(如仅在特定
mode或by_alias=True时生效),可通过info.mode或info.include/exclude判断。 - 如需兼容旧版字段名(如
class_name),只需将result['__name__']替换为result['class_name']即可,语义完全可控。
此方案完美契合“上下文感知序列化”需求:无需每次调用时传参,也不依赖外部状态,所有逻辑封装于基类,一次定义,处处生效。

















