Pydantic处理Union联合类型时匹配错误的解决方案咨询
问题原因
你遇到的两个问题本质都是Pydantic默认的Union类型匹配规则导致的:
- Pydantic默认按Union定义的顺序从左到右尝试匹配类型
- 默认开启隐式类型转换,且默认忽略模型未定义的额外字段,只要输入能满足左侧模型的字段要求(即使有额外字段、或者字段值可以隐式转换为目标类型),就会直接匹配左侧类型,不会继续尝试右侧的类型。
场景1:两个模型存在不同字段(编辑1的断言失败问题)
解法1:给模型添加禁止额外字段的配置
给所有Union涉及的模型添加extra = "forbid"的配置,匹配时如果输入包含当前模型未定义的字段,就会匹配失败,自动尝试Union的下一个类型:
class RequestPayloadPositionsParams(BaseModel): account: str = Field(default="COMBINED ACCOUNT") fields: List[str] = Field(default=["QUANTITY", "OPEN_PRICE", "OPEN_COST"]) class Config: extra = "forbid" # 新增配置 class RequestPayloadOrdersParams(BaseModel): account: str = Field(default="COMBINED ACCOUNT") types: List[str] = Field(default=["WORKING", "FILLED", "CANCELED"]) class Config: extra = "forbid" # 新增配置
修改后第二个断言即可正常通过。
解法2:使用判别器(推荐,性能更高)
如果模型有可以用来区分类型的固定字段(比如你示例里的header.service),可以用判别器明确指定匹配规则,无需逐个尝试类型:
Pydantic V2 示例:
from typing import Annotated, Union from pydantic import Discriminator def get_service_discriminator(v): return v["header"]["service"] if isinstance(v, dict) else v.header.service PayloadItem = Annotated[Union[RequestPayloadLogin, RequestPayloadService], Discriminator(get_service_discriminator)] class RequestPayload(BaseModel): payload: List[PayloadItem] = Field(...)
场景2:两个模型字段完全一致仅字段类型不同(编辑2的类型转换问题)
解法1:开启严格类型匹配+智能Union模式
- 用严格类型
StrictStr、StrictFloat代替普通的str、float,禁止隐式类型转换 - 开启智能Union模式,让Pydantic匹配最符合的类型而非直接取第一个可转换的类型
Pydantic V1 配置:
from pydantic import StrictStr, StrictFloat class ResponseReplacePatchStr(BaseModel): op: str = Field(default="replace") path: str = Field(...) value: StrictStr = Field(...) # 替换为StrictStr class Config: extra = "forbid" smart_union = True # 开启智能Union class ResponseReplacePatchFloat(BaseModel): op: str = Field(default="replace") path: str = Field(...) value: StrictFloat = Field(...) # 替换为StrictFloat class Config: extra = "forbid" smart_union = True # 开启智能Union
Pydantic V2 配置:
from pydantic import StrictStr, StrictFloat class ResponseReplacePatchStr(BaseModel): op: str = Field(default="replace") path: str = Field(...) value: StrictStr = Field(...) model_config = {"extra": "forbid", "union_mode": "smart"} class ResponseReplacePatchFloat(BaseModel): op: str = Field(default="replace") path: str = Field(...) value: StrictFloat = Field(...) model_config = {"extra": "forbid", "union_mode": "smart"}
修改后输入为字符串时会匹配ResponseReplacePatchStr,输入为数字时会匹配ResponseReplacePatchFloat,不会出现强制转换的问题。
解法2:调整Union顺序(临时应急)
如果不想修改模型配置,也可以根据业务场景把匹配优先级更高的类型放在Union左侧,比如大部分场景下value是数字的话,就把ResponseReplacePatchFloat放在Union前面。
内容的提问来源于stack exchange,提问作者Martin Fischer
相关产品推荐
相关产品推荐

