You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Pydantic建模首元素固定、后续元素可重复的API请求列表?

问题翻译

我需要为一个接收JSON对象列表的API创建Pydantic模型,请求负载示例如下:

[{"header": "pets"}, {"meows": true}, {"barks": false}, {"meows": true}]

对应的类型逻辑应该是[Header, Pet, ...]——也就是第一个元素必须是Header类型,后面可以重复多个Pet类型。我尝试过几种方式但都不太满意:

  • 使用Tuple的话,只能定义Tuple[Header, Pet],没法支持多个重复的Pet;
  • 目前用RootModel[list[Union[Header, Pet]]]实现,但需要写大量繁琐的手动验证逻辑。
    如果能让Union自动排除Header(只允许第一个元素是Header),或者采用左到右的匹配逻辑就方便多了,请问有没有更优雅的实现方法?

解决方案:自定义RootModel结合结构验证

针对这种"首元素固定类型,后续元素为可重复类型"的结构,你可以通过自定义RootModel并添加验证器实现,无需手动编写大量重复逻辑,同时保留Pydantic的自动解析和错误提示能力。

1. 定义基础模型

先创建Header和Pet两个核心模型:

from pydantic import BaseModel, RootModel, ValidationError, field_validator
from typing import List, Union

class Header(BaseModel):
    header: str

class Pet(BaseModel):
    meows: bool | None = None
    barks: bool | None = None

    # 确保Pet至少包含一个有效字段
    @field_validator('meows', 'barks', mode='before')
    def check_at_least_one_field(cls, v, values):
        if all(val is None for val in values.data.values()):
            raise ValueError('Pet must have at least one field (meows or barks)')
        return v

2. 自定义Payload模型并添加结构验证

继承RootModel,通过验证器强制首元素为Header、后续元素为Pet:

class Payload(RootModel[List[Union[Header, Pet]]]):
    @field_validator('root', mode='before')
    def validate_payload_structure(cls, value):
        if not isinstance(value, list) or len(value) == 0:
            raise ValueError('Payload must be a non-empty list')
        
        # 验证并解析第一个元素为Header
        try:
            value[0] = Header(**value[0])
        except ValidationError:
            raise ValueError('First element must be a valid Header object')
        
        # 验证并解析剩余元素为Pet
        for idx, item in enumerate(value[1:], start=1):
            try:
                value[idx] = Pet(**item)
            except ValidationError:
                raise ValueError(f'Element at index {idx} is not a valid Pet object')
        
        return value

3. 使用示例

# 有效负载
valid_data = [{"header": "pets"}, {"meows": True}, {"barks": False}, {"meows": True}]
payload = Payload(root=valid_data)
print(payload.root[0])  # Header(header='pets')
print(payload.root[1])  # Pet(meows=True, barks=None)

# 无效负载(首元素不是Header)
invalid_data = [{"meows": True}, {"header": "pets"}]
try:
    Payload(root=invalid_data)
except ValidationError as e:
    print(e)  # 抛出标准验证错误,提示首元素必须是Header

更简洁的实现思路

如果不想手动捕获ValidationError,可以直接在验证器中完成解析,Pydantic会自动抛出标准化错误:

class Payload(RootModel[List[Union[Header, Pet]]]):
    @field_validator('root', mode='before')
    def validate_and_parse(cls, value):
        if not value:
            raise ValueError('Payload cannot be empty')
        
        # 强制首元素为Header,后续为Pet
        return [Header(**value[0])] + [Pet(**item) for item in value[1:]]

为什么不直接用Union?

list[Union[Header, Pet]]的匹配逻辑是尝试所有可能的类型,不会限制Header只能出现在首位置。通过自定义验证器,我们可以强制业务规则,同时保留Pydantic的自动解析和错误提示能力。

内容的提问来源于stack exchange,提问作者Bufke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 22:35:33