Pydantic v2 默认禁止额外字段且校验错误提示不友好,需配置 model_config={"extra": "allow"} 并扁平化 errors() 输出;自定义校验器必须显式 return 值,model_dump() 替代 dict() 且默认行为不同。

Pydantic v2 里 BaseModel 默认不再允许额外字段
很多老项目升级到 Pydantic v2 后,接口接收 JSON 时突然报 ValidationError,提示 “extra fields not permitted”,其实不是数据错了,而是模型默认关掉了未知字段。这和 v1 的宽松行为完全不同。
解决方法是显式开启允许:
from pydantic import BaseModelclass UserRequest(BaseModel): name: str age: int
class Config: extra = "allow" # v1 写法,v2 中已弃用v2 正确写法改用
model_config:class UserRequest(BaseModel): name: str age: intmodel_config = {"extra": "allow"}
-
"allow":忽略多余字段,不报错 -
"ignore":静默丢弃(不推荐,容易掩盖前端传参错误) -
"forbid":严格禁止(默认值,也是最安全的选择)
嵌套结构校验失败时,ValidationError 的 errors() 输出难读
当请求体含多层嵌套(比如 address.city 缺失),默认的 e.errors() 返回的是带 loc 元组的字典,像 [{"loc": ["address", "city"], "msg": "Field required"}],直接返回给前端不友好。
立即学习“Python免费学习笔记(深入)”;
建议封装一层扁平化处理:
def format_pydantic_errors(e):
return [
{"field": ".".join(err["loc"]), "message": err["msg"]}
for err in e.errors()
]
使用示例
try:
UserRequest(**data)
except ValidationError as e:
return {"errors": format_pydantic_errors(e)}
- 避免直接暴露
loc给前端,尤其是内部字段名(如__root__) - 如果用了
Field(alias=...),loc仍显示原始字段名,需手动映射别名 - 批量校验多个对象时,
ValidationError会合并所有错误,errors()仍可用
用 @field_validator 做业务逻辑校验,但忘记 return value
自定义校验器里最常踩的坑:写了逻辑判断却没返回值,导致字段被清空为 None。Pydantic 不会帮你兜底。
比如校验手机号格式后忘了返回:
from pydantic import field_validator@field_validator("phone") def validate_phone(cls, v): if not re.match(r"^1[3-9]\d{9}$", v): raise ValueError("invalid phone number")
❌ 忘了 return v → 字段值变成 None
- 所有
@field_validator函数必须显式return处理后的值(哪怕没改) - 若想支持空值跳过校验,加参数
skip_on_failure=True,但慎用,容易掩盖必填逻辑 - 涉及数据库查重等 IO 操作,不要放在这里——validator 是同步且无 await 支持的
model_dump() 和 dict() 在 v2 中行为不同
v2 废弃了 .dict(),改用 .model_dump()。但很多人迁移时只改函数名,没注意默认参数变化:旧版 .dict(exclude_unset=True) 对应新版的 .model_dump(exclude_unset=True),但默认值变了。
关键差异:
-
.model_dump()默认导出所有字段(含未设置的默认值) -
.model_dump(exclude_unset=True)才等价于旧版.dict() - 如果字段有
default_factory,且没被赋值,exclude_unset=True也不会排除它——得用exclude_defaults=True
API 响应常用组合:
user = UserResponse(name="Alice", age=25) user.model_dump(exclude_unset=True, exclude_none=True)
这样既不传未设置字段,也不传 None 值,更符合 RESTful 接口习惯。
字段级别控制还有 alias、by_alias、exclude 等,但多数场景只需记住:不加 exclude_unset 就很可能把默认值也吐出去,前端拿到意外字段。










