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 unlessrequired=Falseis set on the field. - If
total=False, all fields are optional unlessrequired=Trueis 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:
'ignore'(default): Extra fields are silently dropped.'forbid': Validation fails if extra fields are present.'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: IfTrue, the field is omitted from serialization.serialization_exclude_if: A callable that takes the field value and returnsTrueif 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.