生成并定制 Pydantic JSON Schema

原文标题:JSON Schema
作者:Pydantic 文档贡献者
来源:Pydantic 官方文档
原文:https://pydantic.dev/docs/validation/latest/concepts/json_schema/
页面标示的规范:JSON Schema Draft 2020-12、OpenAPI 3.1.0

Pydantic 能从模型和 Python 类型生成 JSON Schema,并按字段或模型定制生成结果。首先要区分两种输出:JSON Schema 描述数据形状和约束;序列化 JSON 则是某个模型实例的数据。它们不是同一个东西。

生成模型或任意类型的 Schema

对 Pydantic 模型调用 BaseModel.model_json_schema(),会得到可 JSON 化的 Python 字典。需要 JSON 文本时,再用 json.dumps() 序列化这个字典。TypeAdapter.json_schema() 可以为任意受支持的类型生成同类结果,比如 list[int]、联合类型,或由若干模型组成的类型。

import json
from pydantic import BaseModel, Field, TypeAdapter


class Address(BaseModel):
    city: str
    postal_code: str = Field(alias='postalCode')


class User(BaseModel):
    name: str
    age: int = Field(gt=0, description='Age in years')
    address: Address


model_schema = User.model_json_schema()
print(json.dumps(model_schema, indent=2))

list_schema = TypeAdapter(list[int]).json_schema()
print(list_schema)

嵌套模型通常进入根 Schema 的 defs * *,字段通过 * *ref 指向定义。默认情况下,Schema 使用字段别名作为属性名;如果接口契约要使用模型内部字段名,可传 by_alias=False。模型的说明可以来自类文档字符串,字段说明和元数据可由 Field() 提供。

实例序列化使用的是另一组方法:model_dump_json() 序列化 BaseModel 实例;TypeAdapter.dump_json() 序列化适配类型的实例。前者返回 JSON 字符串,后者返回字节。不要把这些序列化 API 当作 Schema 生成方法。

根据输入验证或输出序列化选择模式

model_json_schema() 和 TypeAdapter.json_schema() 都接受 mode 参数。默认的 validation 模式描述 Pydantic 在输入校验时接受的数据形状;serialization 模式描述模型输出的数据形状。当一个 Python 类型的输入和输出表示不同,两个模式的 Schema 也会不同。

官方示例以 Decimal 为例:验证模式允许数字或符合十进制模式的字符串;序列化模式则描述字符串输出。设计请求体和响应体契约时,应根据具体方向选模式,而不是假设一种 Schema 同时覆盖两者。

from decimal import Decimal

from pydantic import BaseModel


class Price(BaseModel):
    amount: Decimal


request_schema = Price.model_json_schema(mode='validation')
response_schema = Price.model_json_schema(mode='serialization')

如果需要将多个根模型及其子模型放进一个顶层 Schema,可用 models_json_schema(),并为每个模型指定模式:

from pydantic import BaseModel
from pydantic.json_schema import models_json_schema


class Item(BaseModel):
    name: str


class Health(BaseModel):
    ok: bool


_, api_schema = models_json_schema(
    [(Item, 'validation'), (Health, 'validation')],
    title='Example API',
)

该方法会在 $defs 中汇总相关模型定义。使用前要确认消费端期待的是这种多模型定义文档,还是每个端点各自独立的根 Schema。

为单个字段补充接口元数据

字段层级的 Schema 通常通过 Field() 定制。常用的 Schema 元数据包括 title、description、examples 和 json_schema_extra;字段约束如 gt、max_length 等也会在适用时转化为 JSON Schema 约束。field_title_generator 可以按字段名与字段信息生成标题。

from pydantic import BaseModel, Field


class Product(BaseModel):
    sku: str = Field(
        title='Product code',
        description='Stable identifier used by the catalog',
    )
    quantity: int = Field(gt=0, examples=[3])

json_schema_extra 可接收字典,为生成的字段或模型 Schema 补充属性;也可接收函数,直接修改 Schema 字典。例如,在确有契约需求时,可以从生成结果中移除 default。这只修改文档描述;若运行时仍有默认值,移除 Schema 中的默认字段不会改变模型行为。

从 Pydantic 2.9 起,附加在嵌套 Annotated 类型上的 json_schema_extra 字典会合并。例如内外两层分别提供 key1 和 key2 时,结果可以同时包含两者。文档说明当前不支持把字典和 callable 两种配置混合组合。

定制模型标题与通用字段标题

模型配置可设置 title、json_schema_extra、json_schema_mode_override、field_title_generator 和 model_title_generator。字段标题生成器可只设在某个字段上,也可在模型配置中应用于全部字段;模型标题生成器接收模型类并返回标题。

from pydantic import BaseModel, ConfigDict


def make_model_title(model: type) -> str:
    return f'API-{model.__name__}'


class Account(BaseModel):
    model_config = ConfigDict(
        field_title_generator=lambda field_name, field_info: field_name.upper(),
        model_title_generator=make_model_title,
    )

    user_id: int
    display_name: str

选择字段或模型级扩展,适用于少数字段、模型或自定义类型;若要改变整套生成策略,则可改用全局生成器。

对自定义类型覆盖或省略 Schema

WithJsonSchema 可以为某个类型直接提供 Schema,适合 Pydantic 默认无法生成 JSON Schema 的类型。它会覆盖该类型完整的生成 Schema,因此替代定义应包含所需的全部信息,例如 type。官方文档建议优先考虑 WithJsonSchema,它比自定义 __get_pydantic_json_schema__() 更简单,也较不容易出错。

from typing import Annotated

from pydantic import BaseModel, WithJsonSchema


SmallInteger = Annotated[
    int,
    WithJsonSchema({'type': 'integer', 'examples': [1, 0, -1]}),
]


class Counter(BaseModel):
    value: SmallInteger

SkipJsonSchema 可从生成结果中省略某个字段或字段 Schema 的一部分。它只影响 JSON Schema 的呈现;它不是字段访问控制,也不会自动改变运行时验证、序列化或权限策略。

更复杂的自定义类型可实现 __get_pydantic_core_schema__() 来定义验证和序列化使用的核心 Schema,也可实现 __get_pydantic_json_schema__() 来修改其 JSON Schema。后者只改变 Schema,不改变用于验证和序列化的 core schema。对 Annotated 元数据,handler 可先取得内层生成的 Schema,再做修改;替换整个核心 Schema 时则要自行提供完整定义。除非确有必要,优先选用标准的字段元数据、WithJsonSchema 或 SkipJsonSchema。

用 GenerateJsonSchema 调整全局输出

要改变全局生成过程,可以继承 GenerateJsonSchema,覆盖其中的生成步骤,再把自定义类传给 model_json_schema() 或 TypeAdapter.json_schema() 的 schema_generator 参数。下面的示例沿用默认生成过程,只改根标题并显式输出当前 dialect:

from pydantic import BaseModel
from pydantic.json_schema import GenerateJsonSchema


class ApiSchemaGenerator(GenerateJsonSchema):
    def generate(self, schema, mode='validation'):
        result = super().generate(schema, mode=mode)
        result['title'] = 'Public API'
        result['$schema'] = self.schema_dialect
        return result


class Event(BaseModel):
    event_id: int


schema = Event.model_json_schema(schema_generator=ApiSchemaGenerator)

自定义生成器还可覆盖排序逻辑、处理无法生成 JSON Schema 的字段等。默认情况下,Pydantic 递归地按键名排序 Schema,但会保留 properties 中模型字段的定义顺序。全局改写能力较强,因此需要为变更后的契约留出清晰的维护边界。

调整 $ref 前缀与定义位置

通过生成方法的 ref_template 参数可以改变 $ref** 字符串的模板,例如改成 **#/components/schemas/{model}**。但 Pydantic 仍把定义放在 **$defs 中;只改引用前缀不会自动将 $defs 搬到 OpenAPI 文档的 components.schemas。生成后若目标格式要求不同的定义位置,还必须在组装 API 文档时处理定义树与所有引用之间的一致性。

schema = User.model_json_schema(
    ref_template='#/components/schemas/{model}'
)

适配目标规范并核对版本

页面介绍的主要输出规范是 JSON Schema Draft 2020-12 与 OpenAPI 3.1.0。Python 类型、约束与自定义类型会被映射为 JSON Schema 的核心类型、验证关键字或 format 扩展。可选值、联合类型、列表和嵌套模型都会以相应的 null、anyOf、items、defs * *与 * *ref 结构体现。

使用映射表时要留意页面自身的例子存在不一致:Decimal 一节的验证/序列化示例将序列化表示为字符串,通用说明也说 Decimal 暴露为字符串;但同页较早的类型映射表把受约束 Decimal 列作 number。位置参数元组等类型的表格说明也需要结合目标 OpenAPI 版本与实际生成结果判断。不要把旧版消费者对 OpenAPI 3.0 的限制直接套用到 3.1;应固定项目实际安装的 Pydantic 版本,生成 Schema 后再按目标规范验证。

还有几项边界值得保留:

  • Optional 字段的 Schema 会说明 null 可作为值;是否必填与是否可为空是两个不同的问题。
  • 使用子模型时,Pydantic 通常把其定义放在 $defs 并引用;对子模型应用自定义标题、说明或默认值后,某些场景会递归内嵌。
  • EmailStr 示例需要安装 email-validator 依赖才能使用。
  • Schema 中的 examples 是公开的说明数据,不要放真实凭据或个人信息。
  • writeOnly: true 是供 Schema 消费者理解字段用途的提示,不会加密字段内容。

页面页脚标示 © Pydantic Services Inc. 2025 to present;正文未列出一份单独的文章转载许可。页面路径使用 latest,并未给出一个固定的 Pydantic 软件版本,因此 API 和映射需要结合项目锁定版本核对。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容