Skip to main content

TypedDict Schemas

TypedDict schemas are the primary mechanism in pydantic-core for validating and serializing Python dictionaries with a fixed set of keys. They provide a structured way to define expected fields, their types, and how to handle unexpected data.

The implementation centers around two main classes in pydantic_core.core_schema:

  • TypedDictSchema: Defines the overall structure and behavior of the dictionary.
  • TypedDictField: Defines the validation and serialization rules for an individual key within that dictionary.

Core Concepts​

A TypedDictSchema is constructed using the typed_dict_schema helper function. It maps string keys to TypedDictField definitions.

Basic Validation​

In its simplest form, a TypedDictSchema ensures that specific keys exist and their values match the provided schemas.

from pydantic_core import SchemaValidator, core_schema

v = SchemaValidator(
core_schema.typed_dict_schema(
fields={
'name': core_schema.typed_dict_field(schema=core_schema.str_schema()),
'age': core_schema.typed_dict_field(schema=core_schema.int_schema()),
}
)
)

# Valid input
assert v.validate_python({'name': 'Alice', 'age': 30}) == {'name': 'Alice', 'age': 30}

Field Configuration​

Each field in the dictionary is defined by a TypedDictField. This class allows for granular control over how each key is treated during validation and serialization.

Totality and Requirement​

The total parameter on TypedDictSchema (defaulting to True) determines if all fields are required by default. This can be overridden on a per-field basis using the required parameter in TypedDictField.

  • If total=True, all fields are required unless required=False is set on the field.
  • If total=False, all fields are optional unless required=True is set on the field.
# total=False makes fields optional by default
v = SchemaValidator(
core_schema.typed_dict_schema(
fields={
'a': core_schema.typed_dict_field(schema=core_schema.int_schema()),
'b': core_schema.typed_dict_field(schema=core_schema.int_schema(), required=True),
},
total=False
)
)

assert v.validate_python({'b': 1}) == {'b': 1} # 'a' is optional

Default Values​

Default values are implemented by wrapping the field's schema in a with_default_schema.

Critical Rule: A field cannot be marked as required=True if it has a default value. Attempting to do so will raise a SchemaError with the message: "Field '{name}': a required field cannot have a default value".

v = SchemaValidator(
core_schema.typed_dict_schema(
fields={
'count': core_schema.typed_dict_field(
schema=core_schema.with_default_schema(
schema=core_schema.int_schema(),
default=0
)
),
}
)
)

assert v.validate_python({}) == {'count': 0}

Handling Extra Fields​

The extra_behavior parameter controls how the validator reacts to keys present in the input that are not defined in the fields dictionary. It accepts three values:

  1. 'ignore' (default): Extra fields are silently dropped.
  2. 'forbid': Validation fails if extra fields are present.
  3. 'allow': Extra fields are preserved in the output.

When extra_behavior='allow', you can use extras_schema to validate the values of these extra fields.

v = SchemaValidator(
core_schema.typed_dict_schema(
fields={'a': core_schema.typed_dict_field(core_schema.int_schema())},
extra_behavior='allow',
extras_schema=core_schema.str_schema() # All extra values must be strings
)
)

assert v.validate_python({'a': 1, 'b': 'hello'}) == {'a': 1, 'b': 'hello'}

Note that extras_schema can only be used if extra_behavior is set to 'allow'. Using it with 'ignore' or 'forbid' will result in a SchemaError.

Aliases and Mapping​

TypedDictField supports complex mapping between input data and the internal dictionary structure using validation_alias.

Simple and Path Aliases​

An alias can be a simple string or a path (list of strings/ints) to extract data from nested structures.

v = SchemaValidator(
core_schema.typed_dict_schema(
fields={
'internal_key': core_schema.typed_dict_field(
schema=core_schema.int_schema(),
validation_alias=['deep', 'nested', 'key']
)
}
)
)

assert v.validate_python({'deep': {'nested': {'key': 42}}}) == {'internal_key': 42}

Multiple Aliases​

You can provide a list of alternative paths. The validator will try each path in order until it finds a match.

field_a = core_schema.typed_dict_field(
schema=core_schema.int_schema(),
validation_alias=[['foo', 'bar'], ['legacy_key']]
)

If validate_by_name is set to True in the CoreConfig, the validator will also check the actual field name if no alias matches.

Serialization​

TypedDictSchema also dictates how dictionaries are converted back to Python objects or JSON.

  • serialization_alias: Defines the key name used in the serialized output.
  • serialization_exclude: If True, the field is omitted from serialization.
  • serialization_exclude_if: A callable that takes the field value and returns True if the field should be excluded.
from pydantic_core import SchemaSerializer

s = SchemaSerializer(
core_schema.typed_dict_schema(
fields={
'secret': core_schema.typed_dict_field(
schema=core_schema.str_schema(),
serialization_exclude=True
),
'public_name': core_schema.typed_dict_field(
schema=core_schema.str_schema(),
serialization_alias='displayName'
)
}
)
)

data = {'secret': 'hidden', 'public_name': 'Alice'}
assert s.to_python(data) == {'displayName': 'Alice'}

When extra_behavior='allow' is used, extra fields are preserved during serialization and typically appear after the defined fields, retaining their original relative order.