
SQLModel 在启用数据库表映射(table=True)时,会绕过 Pydantic v2 的字段验证器(如 @field_validator),导致 mode="before" 等验证逻辑完全不执行,数据直接以原始类型传入数据库,引发类型不匹配错误。
sqlmodel 在启用数据库表映射(`table=true`)时,会绕过 pydantic v2 的字段验证器(如 `@field_validator`),导致 `mode="before"` 等验证逻辑完全不执行,数据直接以原始类型传入数据库,引发类型不匹配错误。
这是 SQLModel 的一个长期设计限制:当模型继承自 SQLModel 且设置 table=True 时,SQLModel 为兼容 SQLAlchemy ORM 行为,在实例化模型对象时跳过了 Pydantic 的完整验证流程(包括 model_validate 和字段级 field_validator)。这意味着即使你使用了 @pydantic.field_validator("data", mode="before"),该装饰器在 POST /foos 解析请求体生成 Foo 实例时也根本不会被调用——foo.data 保持为原始 dict,最终导致数据库插入失败(如 str 字段无法绑定 dict)。
✅ 正确解决方案是分离关注点:使用 Pydantic 模型处理输入验证,再手动转换为 SQLModel 实体。推荐结构如下:
from pydantic import BaseModel, field_validator
import json
# 1. 专用于 API 输入验证的 Pydantic 模型(无 table=True)
class FooInput(BaseModel):
data: str | dict
@field_validator("data", mode="before")
def serialize_dict(cls, value):
if isinstance(value, dict):
return json.dumps(value)
return value
# 2. 仅用于数据库映射的 SQLModel 实体
class Foo(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
data: str # 注意:此处类型必须与序列化后一致
# FastAPI 路由
@app.post("/foos")
def create_foo(
input_data: FooInput, # ✅ 使用 FooInput 进行验证
session: Session = Depends(get_session)
):
# ✅ 手动构建 SQLModel 实例(此时 data 已是 str)
foo = Foo(data=input_data.data) # input_data.data 已为 JSON 字符串
session.add(foo)
session.commit()
session.refresh(foo)
return foo
⚠️ 注意事项:
- 不要对
table=True的 SQLModel 类直接添加@field_validator——它无效; - 避免在 SQLModel 类中重载
__init__或model_post_init来“补救”验证,这易破坏 ORM 兼容性; - 若需复用字段定义,可让
FooInput继承BaseModel并通过typing.Annotated或Field同步约束,但不可共享类本身; - 对于复杂场景(如嵌套模型、多字段联动校验),仍应在输入模型中完成,而非依赖 SQLModel 实体。
简言之:SQLModel 的 table=True 模型本质是「ORM-first」,验证应前置到纯 Pydantic 层。这是设计取舍,而非 bug——理解这一点,才能写出健壮、可维护的 FastAPI + SQLModel 应用。










