コンテンツにスキップ

JSON Schema

API Documentation

pydantic.json_schema

Pydanticでは、モデルからのJSONスキーマの自動作成とカスタマイズが可能です。 生成されたJSONスキーマは、次の仕様に準拠しています。

Generating JSON Schema

JSONスキーマを生成するには、次の関数を使用します。

Note

これらのメソッドをBaseModel.model_dump_jsonおよびTypeAdapter.dump_jsonと混同しないようにしてください。これらのメソッドは、それぞれモデルまたは適応型のインスタンスを直列化します。

これらのメソッドはJSON文字列を返します。これに対して、BaseModel.model_json_schemaTypeAdapter.json_schemaは、それぞれモデルまたは適応型のJSONスキーマを表すJSON可能なdictを返します。

on the "jsonable" nature of JSON schema

model_json_schemaの結果の"json可能"な性質に関しては、いくつかのBaseModel``mjson.dumps(m.model_json_schema())を呼び出すと有効なJSON文字列が返されます。同様に、TypeAdapter.json_schemaでは、json.dumps(TypeAdapter(<some_type>).json_schema())を呼び出すと有効なJSON文字列が返されます。

Tip

Pydanticは以下の両方をサポートしています。

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

一般的に、最初のアプローチはスコープがより狭く、より特定のケースやタイプに合わせてJSONスキーマをカスタマイズできます。2番目のアプローチはスコープがより広く、JSONスキーマ生成プロセス全体をカスタマイズできます。どちらのアプローチでも同じ効果を得ることができますが、ユースケースによっては、一方のアプローチが他方よりも単純なソリューションを提供する場合があります。

以下に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)!

JSON output:

{
  "$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. これはMainModelのスキーマの"json可能な"dictを生成します。
  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'}

次の例に示すように、BaseModelsTypeAdaptersの組み合わせに対して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))

JSON output:

{
  "$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"
    }
  ]
}

Configuring the JsonSchemaMode

model_json_schemaおよびTypeAdapter.json_schemaメソッドのmodeパラメータを使用して、JSONスキーマ生成のモードを指定します。デフォルトでは、モードはモデルの検証スキーマに対応するJSONスキーマを生成する'validation'に設定されています。

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',
}
"""

Customizing JSON Schema

生成されたJSONスキーマは、次の方法でフィールドレベルとモデルレベルの両方でカスタマイズできます。

  1. Fieldコンストラクタを使用したField-level customization
  2. モデルレベルのカスタマイズwithmodel_config

フィールドレベルとモデルレベルの両方で、json_schema_extraオプションを使用してJSONスキーマに追加情報を追加できます。 このオプションの詳細については、以下のUsingjson_schema_extraセクションを参照してください。

カスタムタイプの場合、PydanticはJSONスキーマ生成をカスタマイズするための他のツールを提供しています。

  1. WithJsonSchema annotation
  2. SkipJsonSchema annotation
  3. Implementing __get_pydantic_core_schema__
  4. Implementing __get_pydantic_json_schema__

Field-Level Customization

オプションで、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))

JSON output:

{
  "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"
}

Unenforced Field constraints

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',
}
"""

JSONスキーマの変更は、Fieldコンストラクタのtyping.Annotatedでも指定できます。

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))

JSON output:

{
  "properties": {
    "id": {
      "title": "Id",
      "type": "string"
    },
    "name": {
      "default": "Bar",
      "maxLength": 256,
      "title": "CustomName",
      "type": "string"
    }
  },
  "title": "Foo",
  "type": "object"
}

Programmatic field title generation

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"
}
"""

Model-Level Customization

model configを使用して、モデルでのJSONスキーマ生成をカスタマイズすることもできます。 具体的には、次の設定オプションが関連します。

Using json_schema_extra

json_schema_extraオプションを使用すると、Field levelまたはModel levelのいずれかで、JSONスキーマに追加情報を追加できます。

dictまたはCallablejson_schema_extraに渡すことができます。

Using json_schema_extra with a dict

dictjson_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))

JSON output:

{
  "examples": [
    {
      "a": "Foo"
    }
  ],
  "properties": {
    "a": {
      "title": "A",
      "type": "string"
    }
  },
  "required": [
    "a"
  ],
  "title": "Model",
  "type": "object"
}

Using json_schema_extra with a Callable

Callablejson_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))

JSON output:

{
  "properties": {
    "a": {
      "title": "A",
      "type": "integer"
    }
  },
  "title": "Model",
  "type": "object"
}

Merging json_schema_extra

v2.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

API Documentation

pydantic.json_schema.WithJsonSchema

Tip

カスタム型には、__get_pydantic_json_schema__よりもWithJsonSchema]を使用する方が簡単でエラーが発生しにくくなります。

WithJsonSchemaアノテーションを使用すると、型自体に__get_pydantic_core_schema____get_pydantic_json_schema__を実装しなくても、指定された型に対して生成された(ベース)JSONスキーマをオーバーライドできます。

これは、CallableのようなJSONスキーマを生成するときにエラーを起こす型や、is-instanceコアスキーマを持つ型に対して、JSONスキーマを設定する方法を提供します。

たとえば、次の例でPlainValidatorを使用すると、JSONスキーマを生成するときにエラーが発生します。これは、PlainValidatorCallableであるためです。ただし、WithJsonSchemaアノテーションを使用すると、生成されたJSONスキーマをカスタムMyInt型でオーバーライドできます。

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))

JSON output:

{
  "properties": {
    "a": {
      "examples": [
        1,
        0,
        -1
      ],
      "title": "A",
      "type": "integer"
    }
  },
  "required": [
    "a"
  ],
  "title": "Model",
  "type": "object"
}

Note

ここで説明されているように、将来的にPydanticはPlainValidatorのような型のJSONスキーマ生成の組み込みサポートを追加する可能性がありますが、WithJsonSchemaアノテーションは他のカスタム型にも役立ちます。

SkipJsonSchema annotation

API Documentation

pydantic.json_schema.SkipJsonSchema

[SkipJsonSchema][pydantic.json_schema. SkipJsonSchema]アノテーションを使用すると、生成されたJSONスキーマから包含フィールド(またはフィールドの仕様の一部)をスキップできます。詳細についてはAPIドキュメントを参照してください。

Implementing __get_pydantic_core_schema__

カスタム型(field_name:TheTypeまたはfield_name:Annotated[TheType, .]として使用)およびAnnotatedメタデータ(field_name:Annotated[int, SomeMetadata]として使用)は、__get_pydantic_core_schema__を実装することで、生成されたスキーマを変更または上書きできます。

このメソッドは、2つの位置引数を受け取ります。

  1. この型に対応する型注釈(TheType[T][int]の場合はTheType[int]になります)。
  2. __get_pydantic_core_schema__の次の実装者を呼び出すハンドラ/コールバック。

ハンドラシステムは、mode='wrap'validatorsのように動作します。 この場合、入力は型で、出力は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]
    """

Tip

スキーマをその場で変更する場合でも、スキーマを返さなければならないことに注意してください。

スキーマを完全に上書きするには、ハンドラを呼び出さず、独自の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__のno-opバージョンで次のアプローチを使用できます。

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)],
    )
}
"""

Implementing __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))

JSON output:

{
  "examples": [
    {
      "age": 25,
      "name": "John Doe"
    }
  ],
  "properties": {
    "name": {
      "title": "Name",
      "type": "string"
    },
    "age": {
      "title": "Age",
      "type": "integer"
    }
  },
  "required": [
    "name",
    "age"
  ],
  "title": "Person",
  "type": "object"
}

Using 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"
}
"""

Using 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 schema types

型、カスタムフィールド型、および制約(max_lengthなど)は、次の優先順位で対応する仕様フォーマットにマップされます(同等のものが使用可能な場合)。

  1. JSON Schema Core
  2. JSON Schema Validation
  3. OpenAPI Data Types
  1. 標準のformatJSONフィールドは、より複雑なstringサブタイプのPydantic拡張を定義するために使用されます。

PythonまたはPydanticからJSONスキーマへのフィールドスキーママッピングは、次のように行われます。

Top-level schema generation

また、$defsにモデルと関連するサブモデルのリストのみを含むトップレベルのJSONスキーマを生成することもできます。

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))

JSON output:

{
  "$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

API Documentation

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',
}
"""

Customizing the $refs in JSON Schema

$refのフォーマットは、ref_templateキーワード引数を付けてmodel_json_schema()またはmodel_dump_json()を呼び出すことで変更できます。 定義は常にキー$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,
    )
)

JSON output:

{
  "$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"
}

Miscellaneous Notes on JSON Schema Generation

  • OptionalフィールドのJSONスキーマは、値nullが許可されていることを示しています。
  • Decimal型はJSONスキーマ内で文字列として公開されます(そしてシリアライズされます)。
  • namedtuple型はJSONには存在しないので、モデルのJSONスキーマはnamedtuplenamedtupleとして保存しません。
  • 使用されるサブモデルは、仕様に従って$defsJSON属性に追加され、参照されます。
  • カスタムタイトル、説明、デフォルト値などの変更(Fieldクラスを介して)があるサブモデルは、参照されるのではなく再帰的に含まれます。
  • モデルのdescriptionは、クラスのdocstringまたはFieldクラスへの引数descriptionから取得されます。
  • スキーマはデフォルトでエイリアスをキーとして使用して生成されますが、代わりにmodel_json_schema()またはmodel_dump_json()by_alias=Falseキーワード引数で呼び出すことで、モデルプロパティ名を使用して生成できます。