跳转至

JSON Schema

API 文档

pydantic.json_schema

Pydantic 允许从模型自动创建和自定义 JSON 模式。生成的 JSON 模式符合以下规范:

生成 JSON 模式

使用以下功能生成 JSON 模式:

注意,这些方法不应与 BaseModel.model_dump_json TypeAdapter.dump_json 混淆,它们分别序列化模型或已适配类型的实例。这些方法返回 JSON 字符串。相比之下, BaseModel.model_json_schema TypeAdapter.json_schema 返回一个可 json 化的字典,表示模型或已适配类型的 JSON 模式。

注释

“关于 JSON 模式的”可 JSON 化“性质” 关于 model_json_schema 结果的“可 JSON 化”性质,对某些[ BaseModel ][ m ]调用[ json.dumps(m.model_json_schema()) ]会返回有效的 JSON 字符串。同样,对于 TypeAdapter.json_schema ,调用[ json.dumps(TypeAdapter(<some_type>).json_schema()) ]会返回有效的 JSON 字符串。

提示

Pydantic 同时支持以下两种方式:

  1. Customizing JSON Schema
  2. Customizing the JSON Schema Generation Process

The first approach generally has a more narrow scope, allowing for customization of the JSON schema for more specific cases and types. The second approach generally has a more broad scope, allowing for customization of the JSON schema generation process overall. The same effects can be achieved with either approach, but depending on your use case, one approach might offer a more simple solution than the other.

以下是从一个 BaseModel 生成 JSON 模式的示例:

import json
from enum import Enum
from typing import Union

from typing_extensions import Annotated

from pydantic import BaseModel, Field
from pydantic.config import ConfigDict


class FooBar(BaseModel):
    count: int
    size: Union[float, None] = None


class Gender(str, Enum):
    male = 'male'
    female = 'female'
    other = 'other'
    not_given = 'not_given'


class MainModel(BaseModel):
    """
    This is the description of the main model
    """

    model_config = ConfigDict(title='Main')

    foo_bar: FooBar
    gender: Annotated[Union[Gender, None], Field(alias='Gender')] = None
    snap: int = Field(
        42,
        title='The Snap',
        description='this is the value of snap',
        gt=30,
        lt=50,
    )


main_model_schema = MainModel.model_json_schema()  # (1)!
print(json.dumps(main_model_schema, indent=2))  # (2)!
"""
{
  "$defs": {
    "FooBar": {
      "properties": {
        "count": {
          "title": "Count",
          "type": "integer"
        },
        "size": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Size"
        }
      },
      "required": [
        "count"
      ],
      "title": "FooBar",
      "type": "object"
    },
    "Gender": {
      "enum": [
        "male",
        "female",
        "other",
        "not_given"
      ],
      "title": "Gender",
      "type": "string"
    }
  },
  "description": "This is the description of the main model",
  "properties": {
    "foo_bar": {
      "$ref": "#/$defs/FooBar"
    },
    "Gender": {
      "anyOf": [
        {
          "$ref": "#/$defs/Gender"
        },
        {
          "type": "null"
        }
      ],
      "default": null
    },
    "snap": {
      "default": 42,
      "description": "this is the value of snap",
      "exclusiveMaximum": 50,
      "exclusiveMinimum": 30,
      "title": "The Snap",
      "type": "integer"
    }
  },
  "required": [
    "foo_bar"
  ],
  "title": "Main",
  "type": "object"
}
"""
  1. 这会生成一个可 JSON 化的字典,其中包含 MainModel 的模式。

  2. 在模式字典上调用 json.dumps 会产生一个 JSON 字符串。

TypeAdapter 类允许你创建一个具有方法的对象,用于验证、序列化和为任意类型生成 JSON 模式。这完全取代了 Pydantic V1 中的 schema_of (现在已弃用)。

以下是从 TypeAdapter 生成 JSON 模式的示例:

from typing import List

from pydantic import TypeAdapter

adapter = TypeAdapter(List[int])
print(adapter.json_schema())
#> {'items': {'type': 'integer'}, 'type': 'array'}

你还可以为 BaseModel s TypeAdapter s 的组合生成 JSON 模式,如下例所示:

import json
from typing import Union

from pydantic import BaseModel, TypeAdapter


class Cat(BaseModel):
    name: str
    color: str


class Dog(BaseModel):
    name: str
    breed: str


ta = TypeAdapter(Union[Cat, Dog])
ta_schema = ta.json_schema()
print(json.dumps(ta_schema, indent=2))
"""
{
  "$defs": {
    "Cat": {
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        },
        "color": {
          "title": "Color",
          "type": "string"
        }
      },
      "required": [
        "name",
        "color"
      ],
      "title": "Cat",
      "type": "object"
    },
    "Dog": {
      "properties": {
        "name": {
          "title": "Name",
          "type": "string"
        },
        "breed": {
          "title": "Breed",
          "type": "string"
        }
      },
      "required": [
        "name",
        "breed"
      ],
      "title": "Dog",
      "type": "object"
    }
  },
  "anyOf": [
    {
      "$ref": "#/$defs/Cat"
    },
    {
      "$ref": "#/$defs/Dog"
    }
  ]
}
"""

配置 JsonSchemaMode

通过在 model_json_schema TypeAdapter.json_schema 方法中的 mode 参数指定 JSON 模式生成模式。默认情况下,模式设置为 'validation' ,它生成与模型验证模式相对应的 JSON 模式。

JsonSchemaMode 是一个类型别名,它表示 mode 参数的可用选项:

  • 'validation'
  • 'serialization'

以下是指定 mode 参数的示例,以及它如何影响生成的 JSON 模式:

from decimal import Decimal

from pydantic import BaseModel


class Model(BaseModel):
    a: Decimal = Decimal('12.34')


print(Model.model_json_schema(mode='validation'))
"""
{
    'properties': {
        'a': {
            'anyOf': [{'type': 'number'}, {'type': 'string'}],
            'default': '12.34',
            'title': 'A',
        }
    },
    'title': 'Model',
    'type': 'object',
}
"""

print(Model.model_json_schema(mode='serialization'))
"""
{
    'properties': {'a': {'default': '12.34', 'title': 'A', 'type': 'string'}},
    'title': 'Model',
    'type': 'object',
}
"""

自定义 JSON 模式

通过以下方式可以在字段级别和模型级别自定义生成的 JSON 模式:

  1. 使用 Field 构造函数进行字段级定制

  2. 使用 model_config 进行模型级定制

在字段和模型级别,您都可以使用 json_schema_extra 选项向 JSON 模式添加额外信息。下面的“使用 json_schema_extra ”部分提供了有关此选项的更多详细信息。

对于自定义类型,Pydantic 提供了其他用于自定义 JSON 模式生成的工具:

  1. WithJsonSchema 注释
  2. SkipJsonSchema 注释
  3. 实施 __get_pydantic_core_schema__
  4. 实施 __get_pydantic_json_schema__

字段级定制

可选地,可以使用 Field 函数来提供有关字段和验证的额外信息。

一些字段参数专门用于自定义生成的 JSON 模式:

  • title :字段标题。
  • description :该字段的描述。
  • examples :该字段的示例。
  • json_schema_extra :要添加到字段的其他 JSON 模式属性。
  • field_title_generator :根据字段名称和信息,以编程方式设置字段标题的函数。

以下是一个示例: 翻译文本:

import json

from pydantic import BaseModel, EmailStr, Field, SecretStr


class User(BaseModel):
    age: int = Field(description='Age of the user')
    email: EmailStr = Field(examples=['marcelo@mail.com'])
    name: str = Field(title='Username')
    password: SecretStr = Field(
        json_schema_extra={
            'title': 'Password',
            'description': 'Password of the user',
            'examples': ['123456'],
        }
    )


print(json.dumps(User.model_json_schema(), indent=2))
"""
{
  "properties": {
    "age": {
      "description": "Age of the user",
      "title": "Age",
      "type": "integer"
    },
    "email": {
      "examples": [
        "marcelo@mail.com"
      ],
      "format": "email",
      "title": "Email",
      "type": "string"
    },
    "name": {
      "title": "Username",
      "type": "string"
    },
    "password": {
      "description": "Password of the user",
      "examples": [
        "123456"
      ],
      "format": "password",
      "title": "Password",
      "type": "string",
      "writeOnly": true
    }
  },
  "required": [
    "age",
    "email",
    "name",
    "password"
  ],
  "title": "User",
  "type": "object"
}
"""

未执行的 Field 约束

如果 Pydantic 发现没有被强制执行的约束,将引发错误。如果您想强制约束出现在模式中,即使在解析时没有检查到它,您可以使用带有原始模式属性名称的可变参数来 Field

from pydantic import BaseModel, Field, PositiveInt

try:
    # this won't work since `PositiveInt` takes precedence over the
    # constraints defined in `Field`, meaning they're ignored
    class Model(BaseModel):
        foo: PositiveInt = Field(..., lt=10)

except ValueError as e:
    print(e)


# if you find yourself needing this, an alternative is to declare
# the constraints in `Field` (or you could use `conint()`)
# here both constraints will be enforced:
class ModelB(BaseModel):
    # Here both constraints will be applied and the schema
    # will be generated correctly
    foo: int = Field(..., gt=0, lt=10)


print(ModelB.model_json_schema())
"""
{
    'properties': {
        'foo': {
            'exclusiveMaximum': 10,
            'exclusiveMinimum': 0,
            'title': 'Foo',
            'type': 'integer',
        }
    },
    'required': ['foo'],
    'title': 'ModelB',
    'type': 'object',
}
"""

你也可以通过 Field 构造函数通过 [ typing.Annotated ][] 指定 JSON 模式修改:

import json
from uuid import uuid4

from typing_extensions import Annotated

from pydantic import BaseModel, Field


class Foo(BaseModel):
    id: Annotated[str, Field(default_factory=lambda: uuid4().hex)]
    name: Annotated[str, Field(max_length=256)] = Field(
        'Bar', title='CustomName'
    )


print(json.dumps(Foo.model_json_schema(), indent=2))
"""
{
  "properties": {
    "id": {
      "title": "Id",
      "type": "string"
    },
    "name": {
      "default": "Bar",
      "maxLength": 256,
      "title": "CustomName",
      "type": "string"
    }
  },
  "title": "Foo",
  "type": "object"
}
"""

程序化字段标题生成

field_title_generator 参数可用于根据字段的名称和信息以编程方式生成标题。

请参见以下示例:

import json

from pydantic import BaseModel, Field
from pydantic.fields import FieldInfo


def make_title(field_name: str, field_info: FieldInfo) -> str:
    return field_name.upper()


class Person(BaseModel):
    name: str = Field(field_title_generator=make_title)
    age: int = Field(field_title_generator=make_title)


print(json.dumps(Person.model_json_schema(), indent=2))
"""
{
  "properties": {
    "name": {
      "title": "NAME",
      "type": "string"
    },
    "age": {
      "title": "AGE",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "age"
  ],
  "title": "Person",
  "type": "object"
}
"""

模型级定制

你还可以使用 模型配置 在模型上自定义 JSON 模式生成。具体来说,以下配置选项是相关的:

使用 json_schema_extra

json_schema_extra 选项可用于向 JSON 模式添加额外信息,既可以在字段级别,也可以在模型级别。可以将 dictCallable 传递给 json_schema_extra

使用 json_schema_extradict

你可以将一个 dict 传递给 json_schema_extra ,以向 JSON 模式添加额外信息:

import json

from pydantic import BaseModel, ConfigDict


class Model(BaseModel):
    a: str

    model_config = ConfigDict(json_schema_extra={'examples': [{'a': 'Foo'}]})


print(json.dumps(Model.model_json_schema(), indent=2))
"""
{
  "examples": [
    {
      "a": "Foo"
    }
  ],
  "properties": {
    "a": {
      "title": "A",
      "type": "string"
    }
  },
  "required": [
    "a"
  ],
  "title": "Model",
  "type": "object"
}
"""

使用 json_schema_extraCallable

你可以将一个 Callable 传递给 json_schema_extra ,通过一个函数来修改 JSON 模式:

import json

from pydantic import BaseModel, Field


def pop_default(s):
    s.pop('default')


class Model(BaseModel):
    a: int = Field(default=1, json_schema_extra=pop_default)


print(json.dumps(Model.model_json_schema(), indent=2))
"""
{
  "properties": {
    "a": {
      "title": "A",
      "type": "integer"
    }
  },
  "title": "Model",
  "type": "object"
}
"""

合并 json_schema_extra

从版本 2.9 开始,Pydantic 会合并来自已注释类型的 json_schema_extra 字典。这种模式提供了一种更具添加性的合并方式,而不是之前的覆盖行为。对于在多个类型之间重用 json 模式额外信息的情况,这可能非常有帮助。

我们主要将此更改视为错误修复,因为它解决了 BaseModelTypeAdapter 实例之间 json_schema_extra 合并行为的意外差异——更多详细信息请参见此问题。

import json

from typing_extensions import Annotated, TypeAlias

from pydantic import Field, TypeAdapter

ExternalType: TypeAlias = Annotated[
    int, Field(..., json_schema_extra={'key1': 'value1'})
]

ta = TypeAdapter(
    Annotated[ExternalType, Field(..., json_schema_extra={'key2': 'value2'})]
)
print(json.dumps(ta.json_schema(), indent=2))
"""
{
  "key1": "value1",
  "key2": "value2",
  "type": "integer"
}
"""

如果你希望最后一个 json_schema_extra 规范覆盖前面的规范,你可以使用 callable 进行更重大的更改,包括添加或删除键,或修改值。如果你希望模仿 Pydantic v2.8 及更早版本中存在的 json_schema_extra 覆盖的行为,可以使用此模式:

import json

from typing_extensions import Annotated, TypeAlias

from pydantic import Field, TypeAdapter
from pydantic.json_schema import JsonDict

ExternalType: TypeAlias = Annotated[
    int, Field(..., json_schema_extra={'key1': 'value1', 'key2': 'value2'})
]


def finalize_schema(s: JsonDict) -> None:
    s.pop('key1')
    s['key2'] = s['key2'] + '-final'
    s['key3'] = 'value3-final'


ta = TypeAdapter(
    Annotated[ExternalType, Field(..., json_schema_extra=finalize_schema)]
)
print(json.dumps(ta.json_schema(), indent=2))
"""
{
  "key2": "value2-final",
  "key3": "value3-final",
  "type": "integer"
}
"""

WithJsonSchema annotation

WithJsonSchema 注释

API Documentation

pydantic.json_schema.WithJsonSchema

API 文档

pydantic.json_schema.WithJsonSchema

提示

使用 WithJsonSchema ] 优于为自定义类型实现 __get_pydantic_json_schema__ ,因为它更简单且错误更少。

WithJsonSchema 注释可用于覆盖给定类型生成的(基本)JSON 模式,而无需在类型本身实现 __get_pydantic_core_schema____get_pydantic_json_schema__

这为设置 JSON 模式提供了一种方法,对于否则会在生成 JSON 模式时引发错误的类型,例如 Callable ,或具有 is-instance 核心模式的类型。

例如,在以下示例中使用 PlainValidator 会在生成 JSON 模式时引发错误,因为 PlainValidator 是一个 Callable 。但是,通过使用 WithJsonSchema 注释,我们可以覆盖自定义 MyInt 类型生成的 JSON 模式:

import json

from typing_extensions import Annotated

from pydantic import BaseModel, PlainValidator, WithJsonSchema

MyInt = Annotated[
    int,
    PlainValidator(lambda v: int(v) + 1),
    WithJsonSchema({'type': 'integer', 'examples': [1, 0, -1]}),
]


class Model(BaseModel):
    a: MyInt


print(Model(a='1').a)
#> 2

print(json.dumps(Model.model_json_schema(), indent=2))
"""
{
  "properties": {
    "a": {
      "examples": [
        1,
        0,
        -1
      ],
      "title": "A",
      "type": "integer"
    }
  },
  "required": [
    "a"
  ],
  "title": "Model",
  "type": "object"
}
"""

注意

正如本问题中所讨论的,未来,Pydantic 很可能会为 PlainValidator 等类型添加内置的 JSON 模式生成支持,但 WithJsonSchema 注释对于其他自定义类型仍然有用。

SkipJsonSchema annotation

SkipJsonSchema 注释

API Documentation

pydantic.json_schema.SkipJsonSchema

API 文档

pydantic.json_schema.SkipJsonSchema

SkipJsonSchema 注释可用于从生成的 JSON 模式中跳过包含的字段(或字段规范的一部分)。有关更多详细信息,请参阅 API 文档。

实施 __get_pydantic_core_schema__

自定义类型(用作 field_name: TheTypefield_name: Annotated[TheType, ...] )以及 Annotated 元数据(用作 field_name: Annotated[int, SomeMetadata] )可以通过实现 __get_pydantic_core_schema__ 来修改或覆盖生成的架构。此方法接收两个位置参数:

  1. 与该类型对应的类型注释(因此,在 TheType[T][int] 的情况下,它将是 TheType[int] )。

  2. 调用 __get_pydantic_core_schema__ 的下一个实现者的处理程序/回调。

处理程序系统的工作方式与 mode='wrap' 验证器类似。在这种情况下,输入是类型,输出是 core_schema

以下是一个自定义类型的示例,该类型覆盖了生成的 core_schema

from dataclasses import dataclass
from typing import Any, Dict, List, Type

from pydantic_core import core_schema

from pydantic import BaseModel, GetCoreSchemaHandler


@dataclass
class CompressedString:
    dictionary: Dict[int, str]
    text: List[int]

    def build(self) -> str:
        return ' '.join([self.dictionary[key] for key in self.text])

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source: Type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        assert source is CompressedString
        return core_schema.no_info_after_validator_function(
            cls._validate,
            core_schema.str_schema(),
            serialization=core_schema.plain_serializer_function_ser_schema(
                cls._serialize,
                info_arg=False,
                return_schema=core_schema.str_schema(),
            ),
        )

    @staticmethod
    def _validate(value: str) -> 'CompressedString':
        inverse_dictionary: Dict[str, int] = {}
        text: List[int] = []
        for word in value.split(' '):
            if word not in inverse_dictionary:
                inverse_dictionary[word] = len(inverse_dictionary)
            text.append(inverse_dictionary[word])
        return CompressedString(
            {v: k for k, v in inverse_dictionary.items()}, text
        )

    @staticmethod
    def _serialize(value: 'CompressedString') -> str:
        return value.build()


class MyModel(BaseModel):
    value: CompressedString


print(MyModel.model_json_schema())
"""
{
    'properties': {'value': {'title': 'Value', 'type': 'string'}},
    'required': ['value'],
    'title': 'MyModel',
    'type': 'object',
}
"""
print(MyModel(value='fox fox fox dog fox'))
"""
value = CompressedString(dictionary={0: 'fox', 1: 'dog'}, text=[0, 0, 0, 1, 0])
"""

print(MyModel(value='fox fox fox dog fox').model_dump(mode='json'))
#> {'value': 'fox fox fox dog fox'}

由于 Pydantic 不知道如何为 CompressedString 生成模式,如果在其 __get_pydantic_core_schema__ 方法中调用 handler(source) ,则会得到一个 pydantic.errors.PydanticSchemaGenerationError 错误。对于大多数自定义类型来说都是这种情况,因此几乎从不希望为自定义类型调用 handler

Annotated 元数据的处理过程大致相同,只是通常可以调用 handler 让 Pydantic 处理生成模式。

from dataclasses import dataclass
from typing import Any, Sequence, Type

from pydantic_core import core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler, ValidationError


@dataclass
class RestrictCharacters:
    alphabet: Sequence[str]

    def __get_pydantic_core_schema__(
        self, source: Type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        if not self.alphabet:
            raise ValueError('Alphabet may not be empty')
        schema = handler(
            source
        )  # get the CoreSchema from the type / inner constraints
        if schema['type'] != 'str':
            raise TypeError('RestrictCharacters can only be applied to strings')
        return core_schema.no_info_after_validator_function(
            self.validate,
            schema,
        )

    def validate(self, value: str) -> str:
        if any(c not in self.alphabet for c in value):
            raise ValueError(
                f'{value!r} is not restricted to {self.alphabet!r}'
            )
        return value


class MyModel(BaseModel):
    value: Annotated[str, RestrictCharacters('ABC')]


print(MyModel.model_json_schema())
"""
{
    'properties': {'value': {'title': 'Value', 'type': 'string'}},
    'required': ['value'],
    'title': 'MyModel',
    'type': 'object',
}
"""
print(MyModel(value='CBA'))
#> value='CBA'

try:
    MyModel(value='XYZ')
except ValidationError as e:
    print(e)
    """
    1 validation error for MyModel
    value
      Value error, 'XYZ' is not restricted to 'ABC' [type=value_error, input_value='XYZ', input_type=str]
    """

到目前为止,我们一直在包装模式,但如果你只是想修改它或忽略它,也可以这样做。

要修改模式,首先调用处理程序,然后修改结果:

from typing import Any, Type

from pydantic_core import ValidationError, core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler


class SmallString:
    def __get_pydantic_core_schema__(
        self,
        source: Type[Any],
        handler: GetCoreSchemaHandler,
    ) -> core_schema.CoreSchema:
        schema = handler(source)
        assert schema['type'] == 'str'
        schema['max_length'] = 10  # modify in place
        return schema


class MyModel(BaseModel):
    value: Annotated[str, SmallString()]


try:
    MyModel(value='too long!!!!!')
except ValidationError as e:
    print(e)
    """
    1 validation error for MyModel
    value
      String should have at most 10 characters [type=string_too_long, input_value='too long!!!!!', input_type=str]
    """

注意,即使您只是就地修改模式,也必须返回一个模式。

要完全覆盖模式,请不要调用处理程序并返回您自己的 CoreSchema

from typing import Any, Type

from pydantic_core import ValidationError, core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler


class AllowAnySubclass:
    def __get_pydantic_core_schema__(
        self, source: Type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        # we can't call handler since it will fail for arbitrary types
        def validate(value: Any) -> Any:
            if not isinstance(value, source):
                raise ValueError(
                    f'Expected an instance of {source}, got an instance of {type(value)}'
                )

        return core_schema.no_info_plain_validator_function(validate)


class Foo:
    pass


class Model(BaseModel):
    f: Annotated[Foo, AllowAnySubclass()]


print(Model(f=Foo()))
#> f=None


class NotFoo:
    pass


try:
    Model(f=NotFoo())
except ValidationError as e:
    print(e)
    """
    1 validation error for Model
    f
      Value error, Expected an instance of <class '__main__.Foo'>, got an instance of <class '__main__.NotFoo'> [type=value_error, input_value=<__main__.NotFoo object at 0x0123456789ab>, input_type=NotFoo]
    """

如上文所见,使用 BaseModel 类型注释一个字段可以用于修改或覆盖生成的 JSON 模式。但是,如果您想利用 Annotated 存储元数据,但又不想覆盖生成的 JSON 模式,则可以在元数据类上实现一个空操作版本的 __get_pydantic_core_schema__ 来采用以下方法:

from typing import Type

from pydantic_core import CoreSchema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler


class Metadata(BaseModel):
    foo: str = 'metadata!'
    bar: int = 100

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Type[BaseModel], handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        if cls is not source_type:
            return handler(source_type)
        return super().__get_pydantic_core_schema__(source_type, handler)


class Model(BaseModel):
    state: Annotated[int, Metadata()]


m = Model.model_validate({'state': 2})
print(repr(m))
#> Model(state=2)
print(m.model_fields)
"""
{
    'state': FieldInfo(
        annotation=int,
        required=True,
        metadata=[Metadata(foo='metadata!', bar=100)],
    )
}
"""

实施 __get_pydantic_json_schema__

你还可以实现 __get_pydantic_json_schema__ 来修改或覆盖生成的 json 模式。修改此方法仅影响 JSON 模式——它不会影响用于验证和序列化的核心模式。

以下是修改生成的 JSON 模式的示例:

import json
from typing import Any

from pydantic_core import core_schema as cs

from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler, TypeAdapter
from pydantic.json_schema import JsonSchemaValue


class Person:
    name: str
    age: int

    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> cs.CoreSchema:
        return cs.typed_dict_schema(
            {
                'name': cs.typed_dict_field(cs.str_schema()),
                'age': cs.typed_dict_field(cs.int_schema()),
            },
        )

    @classmethod
    def __get_pydantic_json_schema__(
        cls, core_schema: cs.CoreSchema, handler: GetJsonSchemaHandler
    ) -> JsonSchemaValue:
        json_schema = handler(core_schema)
        json_schema = handler.resolve_ref_schema(json_schema)
        json_schema['examples'] = [
            {
                'name': 'John Doe',
                'age': 25,
            }
        ]
        json_schema['title'] = 'Person'
        return json_schema


print(json.dumps(TypeAdapter(Person).json_schema(), indent=2))
"""
{
  "examples": [
    {
      "age": 25,
      "name": "John Doe"
    }
  ],
  "properties": {
    "name": {
      "title": "Name",
      "type": "string"
    },
    "age": {
      "title": "Age",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "age"
  ],
  "title": "Person",
  "type": "object"
}
"""

使用 field_title_generator

field_title_generator 参数可用于根据字段的名称和信息以编程方式生成字段的标题。这类似于字段级别的 field_title_generator ,但 ConfigDict 选项将应用于类的所有字段。

请参见以下示例:

import json

from pydantic import BaseModel, ConfigDict


class Person(BaseModel):
    model_config = ConfigDict(
        field_title_generator=lambda field_name, field_info: field_name.upper()
    )
    name: str
    age: int


print(json.dumps(Person.model_json_schema(), indent=2))
"""
{
  "properties": {
    "name": {
      "title": "NAME",
      "type": "string"
    },
    "age": {
      "title": "AGE",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "age"
  ],
  "title": "Person",
  "type": "object"
}
"""

使用 model_title_generator

model_title_generator 配置选项类似于 field_title_generator 选项,但它适用于模型本身的标题,并接受模型类作为输入。

请参见以下示例:

import json
from typing import Type

from pydantic import BaseModel, ConfigDict


def make_title(model: Type) -> str:
    return f'Title-{model.__name__}'


class Person(BaseModel):
    model_config = ConfigDict(model_title_generator=make_title)
    name: str
    age: int


print(json.dumps(Person.model_json_schema(), indent=2))
"""
{
  "properties": {
    "name": {
      "title": "Name",
      "type": "string"
    },
    "age": {
      "title": "Age",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "age"
  ],
  "title": "Title-Person",
  "type": "object"
}
"""

JSON 模式类型

类型、自定义字段类型和约束(如 max_length )按照以下优先级顺序映射到相应的规范格式(当有等效格式时):

  1. JSON 模式核心
  2. JSON 模式验证
  3. OpenAPI 数据类型
  4. 标准的 format JSON 字段用于定义 Pydantic 扩展,以支持更复杂的 string 子类型。

从 Python 或 Pydantic 到 JSON 模式的字段模式映射如下进行:

顶级模式生成

你还可以生成一个顶级 JSON 模式,该模式在其 $defs 中仅包含模型列表和相关子模型:

import json

from pydantic import BaseModel
from pydantic.json_schema import models_json_schema


class Foo(BaseModel):
    a: str = None


class Model(BaseModel):
    b: Foo


class Bar(BaseModel):
    c: int


_, top_level_schema = models_json_schema(
    [(Model, 'validation'), (Bar, 'validation')], title='My Schema'
)
print(json.dumps(top_level_schema, indent=2))
"""
{
  "$defs": {
    "Bar": {
      "properties": {
        "c": {
          "title": "C",
          "type": "integer"
        }
      },
      "required": [
        "c"
      ],
      "title": "Bar",
      "type": "object"
    },
    "Foo": {
      "properties": {
        "a": {
          "default": null,
          "title": "A",
          "type": "string"
        }
      },
      "title": "Foo",
      "type": "object"
    },
    "Model": {
      "properties": {
        "b": {
          "$ref": "#/$defs/Foo"
        }
      },
      "required": [
        "b"
      ],
      "title": "Model",
      "type": "object"
    }
  },
  "title": "My Schema"
}
"""

Customizing the JSON Schema Generation Process

自定义 JSON 模式生成过程

API 文档

pydantic.json_schema

如果需要自定义模式生成,可以使用 schema_generator ,根据应用程序的需要修改 GenerateJsonSchema 类。

可以用于生成 JSON 模式的各种方法接受一个关键字参数 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema ,您可以将自定义子类传递给这些方法,以使用自己的方法生成 JSON 模式。

GenerateJsonSchema 实现了将类型的 pydantic-core 模式转换为 JSON 模式的功能。从设计上讲,此类将 JSON 模式生成过程分解为较小的方法,可以在子类中轻松覆盖这些方法,以修改生成 JSON 模式的“全局”方法。

from pydantic import BaseModel
from pydantic.json_schema import GenerateJsonSchema


class MyGenerateJsonSchema(GenerateJsonSchema):
    def generate(self, schema, mode='validation'):
        json_schema = super().generate(schema, mode=mode)
        json_schema['title'] = 'Customize title'
        json_schema['$schema'] = self.schema_dialect
        return json_schema


class MyModel(BaseModel):
    x: int


print(MyModel.model_json_schema(schema_generator=MyGenerateJsonSchema))
"""
{
    'properties': {'x': {'title': 'X', 'type': 'integer'}},
    'required': ['x'],
    'title': 'Customize title',
    'type': 'object',
    '$schema': 'https://json-schema.org/draft/2020-12/schema',
}
"""

以下是一种可以用来排除模式中没有有效 JSON 模式的任何字段的方法:

from typing import Callable

from pydantic_core import PydanticOmit, core_schema

from pydantic import BaseModel
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue


class MyGenerateJsonSchema(GenerateJsonSchema):
    def handle_invalid_for_json_schema(
        self, schema: core_schema.CoreSchema, error_info: str
    ) -> JsonSchemaValue:
        raise PydanticOmit


def example_callable():
    return 1


class Example(BaseModel):
    name: str = 'example'
    function: Callable = example_callable


instance_example = Example()

validation_schema = instance_example.model_json_schema(
    schema_generator=MyGenerateJsonSchema, mode='validation'
)
print(validation_schema)
"""
{
    'properties': {
        'name': {'default': 'example', 'title': 'Name', 'type': 'string'}
    },
    'title': 'Example',
    'type': 'object',
}
"""

在 JSON Schema 中自定义 $ref

$ref 的格式可以通过调用 model_json_schema() model_dump_json() 并使用 ref_template 关键字参数来更改。定义始终存储在 $defs 键下,但可以为引用指定指定的前缀。

如果需要扩展或修改 JSON 模式默认定义的位置,这将很有用。例如,对于 OpenAPI:

import json

from pydantic import BaseModel
from pydantic.type_adapter import TypeAdapter


class Foo(BaseModel):
    a: int


class Model(BaseModel):
    a: Foo


adapter = TypeAdapter(Model)

print(
    json.dumps(
        adapter.json_schema(ref_template='#/components/schemas/{model}'),
        indent=2,
    )
)
"""
{
  "$defs": {
    "Foo": {
      "properties": {
        "a": {
          "title": "A",
          "type": "integer"
        }
      },
      "required": [
        "a"
      ],
      "title": "Foo",
      "type": "object"
    }
  },
  "properties": {
    "a": {
      "$ref": "#/components/schemas/Foo"
    }
  },
  "required": [
    "a"
  ],
  "title": "Model",
  "type": "object"
}
"""

JSON 模式生成杂记

  • Optional 字段的 JSON 模式表明允许的值为 null
  • Decimal 类型在 JSON 模式(和序列化)中暴露为字符串。
  • 由于 namedtuple 类型在 JSON 中不存在,因此模型的 JSON 模式不会将 namedtuple 保留为 namedtuple
  • 根据规范,将子模型添加到 $defs JSON 属性中并引用。
  • 具有修改的子模型(通过 Field 类),例如自定义标题、描述或默认值,会被递归包含,而不是被引用。
  • 模型的 description 取自类的文档字符串或 Field 类的 description 参数。
  • 该模式默认使用别名作为键生成,但也可以通过调用 model_json_schema() model_dump_json() 并使用 by_alias=False 关键字参数使用模型属性名称生成。

本文总阅读量