
当 Pydantic 字段使用 Literal 类型(如 "enabled" | "disabled")时,空字符串 "" 会直接触发类型校验失败,而非应用默认值;本文介绍通过 @field_validator(mode="before") 在解析前拦截并替换空值,实现优雅的默认值回退机制。
当 pydantic 字段使用 literal 类型(如 `"enabled" | "disabled"`)时,空字符串 `""` 会直接触发类型校验失败,而非应用默认值;本文介绍通过 `@field_validator(mode="before")` 在解析前拦截并替换空值,实现优雅的默认值回退机制。
在 Pydantic v2+ 中,Literal 类型字段对输入值执行严格字面量匹配——这意味着只有精确等于 "enabled" 或 "disabled" 的字符串才可通过校验;而空字符串 ""、None 或其他任意字符串均会被立即拒绝,并抛出 literal_error,甚至不会进入自定义验证器逻辑(这也是你发现 @validator 完全未被调用的原因)。
要解决该问题,关键在于:在 Pydantic 内置类型校验发生之前,预先处理非法但语义可修复的输入(如空字符串)。这正是 mode="before" 验证器的设计目的。
以下为完整、可运行的解决方案:
from pydantic import BaseModel, Field, field_validator
from typing_extensions import Annotated, Literal
ENABLED_DISABLED = Literal["disabled", "enabled"]
class GlobalSchema(BaseModel):
location: Annotated[
ENABLED_DISABLED,
Field(description="Location: 'enabled' or 'disabled'")
] = "disabled"
@field_validator("location", mode="before")
def validate_location(cls, value):
# 若输入为空字符串,主动替换为字段默认值
if value == "":
return cls.model_fields["location"].default
return value # 其他值交由后续内置校验(Literal 检查)
✅ 效果验证:
# 空字符串 → 自动转为默认值 "disabled"
print(GlobalSchema(location="")) # location='disabled'
# 合法值 → 正常通过
print(GlobalSchema(location="enabled")) # location='enabled'
# 非法值 → 抛出清晰错误(保留原始校验逻辑)
try:
GlobalSchema(location="foo")
except Exception as e:
print(e)
# 输出包含:Input should be 'disabled' or 'enabled'
⚠️ 注意事项:
- 必须使用 mode="before"(Pydantic v2 推荐写法),旧版 @validator(..., pre=True) 已弃用;
- 不要尝试在 mode="after" 或普通验证器中修复——此时校验早已失败,无法抵达;
- cls.model_fields["location"].default 是获取字段默认值的安全方式(支持 Field(default=...) 和直接赋值两种声明);
- 若需同时处理 None、空白字符串(如 " ")或 0 等,可在 before 验证器中扩展判断逻辑,但需谨慎避免掩盖真实错误输入。
总结:Pydantic 的校验流程是分阶段的,mode="before" 是干预原始输入的“第一道闸门”。合理利用它,既能保持类型安全,又能赋予字段更健壮的容错能力。










