
本文详解如何在 Pydantic 2.7.0 中定义字段,使其既能接受符合 "%Y-%m-%d %H:%M:%S" 格式的日期时间字符串,也能将空字符串(如 "" 或仅空白字符)安全转换为 None,避免验证失败。
本文详解如何在 pydantic 2.7.0 中定义字段,使其既能接受符合 "%y-%m-%d %h:%m:%s" 格式的日期时间字符串,也能将空字符串(如 `""` 或仅空白字符)安全转换为 `none`,避免验证失败。
在 Pydantic v2 中,BeforeValidator 的执行时机是关键:它在默认类型校验之前运行,但不会绕过后续的类型约束。因此,若注解为 Annotated[datetime, BeforeValidator(...)],则 Pydantic 仍会强制要求最终值必须是 datetime 类型——即使你的预处理函数返回了 None,也会因类型不匹配而报错:Input should be a valid datetime [type=datetime_type, input_value=None, input_type=NoneType]。
✅ 正确做法是:将 None 显式纳入类型注解,并确保 BeforeValidator 的返回值与之兼容。即使用 Annotated[datetime | None, BeforeValidator(...)],而非仅 Annotated[datetime, ...]。这样,Pydantic 的内部验证器会接受 datetime 或 None 两种合法输入,预处理逻辑才能真正生效。
以下是完整、健壮的实现示例:
from pydantic import BaseModel, BeforeValidator
from typing import Annotated
from datetime import datetime
def parse_datetime(value: str | None) -> datetime | None:
# 支持原始 None 输入(如 {"dt": None}),也支持字符串
if value is None:
return None
if isinstance(value, str) and not value.strip():
return None
try:
return datetime.strptime(value.strip(), "%Y-%m-%d %H:%M:%S")
except ValueError as e:
raise ValueError(f"Invalid datetime format. Expected 'YYYY-MM-DD HH:MM:SS', got '{value}'") from e
# 关键:类型注解必须包含 datetime | None
DateTimeOrNone = Annotated[datetime | None, BeforeValidator(parse_datetime)]
class Model(BaseModel):
dt: DateTimeOrNone
# ✅ 全部通过验证
print(Model.model_validate({"dt": "2024-05-20 14:30:00"})) # dt=datetime(2024, 5, 20, 14, 30)
print(Model.model_validate({"dt": ""})) # dt=None
print(Model.model_validate({"dt": " "})) # dt=None
print(Model.model_validate({"dt": None})) # dt=None⚠️ 注意事项:
- 不要使用 PlainValidator 替代 BeforeValidator 来“绕过”内置校验——虽然它能工作,但会丢失 Pydantic 对 datetime 类型的自动序列化、JSON Schema 生成及错误提示等核心能力;
- parse_datetime 函数应显式处理 None 和空白字符串,提升鲁棒性;
- 错误信息建议携带上下文(如原始输入值),便于调试;
- 若需支持更多格式(如 ISO 8601),推荐改用 datetime.fromisoformat() 或第三方库(如 dateutil.parser),但务必保持返回类型契约(datetime | None)。
总结:Pydantic 的类型注解即契约。BeforeValidator 是预处理器,不是替代器;要让 None 合法,就必须在类型层面声明它——这是理解 Pydantic v2 验证流程的核心原则。

















