如何让Pydantic支持含两种可选字典类型的列表验证?
解决方案
原因分析
你当前的SourceInfoItem模型定义了两个可选字段,但Pydantic依然报错,是因为模型默认期望每个列表项的结构同时包含这两个字段(即使值为None),但你的JSON数据中每个对象仅包含其中一个键。这种场景属于互斥的多态类型,需要用联合类型而非包含可选字段的模型来处理。
正确实现方式(Pydantic v2)
使用Pydantic的DiscriminatedUnion定义互斥类型,指定每个类型对应的鉴别键:
1. 导入必要模块
from typing import List from pydantic import BaseModel, DiscriminatedUnion
2. 保留子模型定义
保持原有的Ec2AssetSourceSimple和AssetSource不变:
class Ec2AssetSourceSimple(BaseModel): instanceType: str subnetId: str imageId: str groupName: str accountId: str macAddress: str reservationId: str monitoringEnabled: str spotInstance: str zone: str instanceState: str privateDnsName: str vpcId: str type: str availabilityZone: str privateIpAddress: str firstDiscovered: str ec2InstanceTags: dict publicIpAddress: str lastUpdated: str region: str assetId: int groupId: str localHostname: str publicDnsName: str class AssetSource(BaseModel): pass
3. 定义互斥联合类型
创建联合类型,将每个子模型与对应的外层键绑定:
SourceInfoItem = DiscriminatedUnion( { "Ec2AssetSourceSimple": Ec2AssetSourceSimple, "AssetSource": AssetSource, } )
4. 修改顶层模型
更新SourceInfo和QualysHost的字段类型:
class SourceInfo(BaseModel): list: List[SourceInfoItem] class QualysHost(BaseModel): sourceInfo: SourceInfo
5. 验证数据
现在解析JSON时,Pydantic会自动识别每个列表项的类型,无需强制两个字段都存在:
json_data = { "sourceInfo": { "list": [ {"Ec2AssetSourceSimple": {"instanceType": "t2.micro", ...}}, {"AssetSource": {}} ] } } host = QualysHost(**json_data) print(host.sourceInfo.list[0].Ec2AssetSourceSimple.instanceType) # 输出 t2.micro
兼容Pydantic v1的替代方案
如果使用Pydantic v1,可通过root_validator实现字段互斥校验:
from typing import List, Optional from pydantic import BaseModel, root_validator class SourceInfoItem(BaseModel): Ec2AssetSourceSimple: Optional[Ec2AssetSourceSimple] = None AssetSource: Optional[AssetSource] = None @root_validator(pre=True) def check_exactly_one_field(cls, values): allowed_fields = ["Ec2AssetSourceSimple", "AssetSource"] present_fields = [k for k in values if k in allowed_fields] if len(present_fields) != 1: raise ValueError(f"必须且仅包含以下字段之一: {allowed_fields}") return values class Config: extra = "forbid" # 禁止额外字段 class SourceInfo(BaseModel): list: List[SourceInfoItem] class QualysHost(BaseModel): sourceInfo: SourceInfo
内容的提问来源于stack exchange,提问作者shluvme
相关产品推荐
相关产品推荐

