pydantic v2 如何自定义字段验证器(field_validator vs field_serializer)

field_validator用于校验并可选修改已解析字段值,不负责类型转换;它在解析阶段运行,接收同类型输入、返回同类型输出,多个按定义顺序执行,不可做I/O;field_serializer则仅在序列化时控制输出格式,与输入校验无关。

field_validator 用来改值或抛错,不是做类型转换的

它在数据解析阶段运行,作用是校验并可选地修改字段原始输入值。比如把字符串转成

小写、检查邮箱格式、拒绝空字符串等。注意它不负责类型转换——那是 BeforeValidator 或模型字段类型本身该干的事。

  • field_validator 接收的是已初步解析后的值(比如 str 字段收到的就是字符串,不是原始 JSON 字符串)
  • 必须返回同类型的值,或抛出 ValueError/TypeError;返回其他类型会静默失败或触发后续类型错误
  • 多个 @field_validator 按定义顺序执行,前一个的返回值是后一个的输入
  • 别在里面做 I/O 或耗时操作,它属于解析路径,影响初始化性能

field_serializer 控制输出,不影响输入和校验

它只在调用 model.model_dump()model.model_dump_json()print(model) 等序列化场景触发,对字段赋值、校验、内部存储完全无感。典型用途是脱敏、格式标准化、添加计算字段。

  • 默认不启用,需显式传 mode='json'mode='python' 才生效;不加 mode 参数时不会被调用
  • 接收的是字段当前存储的值(比如一个 datetime 对象),不是原始输入
  • 可以返回任意可序列化的类型(如 str 替代 datetime),但要确保与 return_type 注解一致,否则 IDE 和类型检查可能报错
  • 如果同时定义了 @field_validator@field_serializer,它们互不干扰:一个管“进”,一个管“出”

别把 field_validator 当成 before_validator 用

常见误操作是想在 field_validator 里把 str 转成 int,结果报 Input should be a valid integer。这是因为 Pydantic v2 的解析流程是:原始输入 → 类型转换(由字段类型驱动)→ field_validator。类型转换失败发生在 validator 之前,根本进不到你的函数里。

  • 真需要预处理原始输入(比如把 "123" 字符串转成 int),得用 BeforeValidator,配合 Annotated
  • 例如:age: Annotated[int, BeforeValidator(lambda x: int(x) if isinstance(x, str) else x)]
  • field_validator 适合在类型已确定的前提下做业务逻辑校验,比如 if value

serializer 返回 None 会导致字段被忽略(除非显式设 exclude_none=False)

这是容易被忽略的坑。当 @field_serializer 返回 None,Pydantic 默认在 model_dump() 中跳过该字段(类似 exclude_unset=True 的行为),而不是保留 null

  • 若需输出 null,必须返回 None 并设置 when_used='always' + 显式传 exclude_none=False
  • 更稳妥的做法是避免返回 None,改用空字符串、默认值或专门的标记对象
  • 调试时可用 print() 在 serializer 里确认是否被调用——它只在序列化时触发,赋值时完全不执行
字段验证和序列化的边界在 v2 里划得很清:一个守入口,一个管出口。混用或期待它做类型转换,基本都会卡在解析阶段报错。