Structured Mappings with TypedDict
TypedDictSchema is the core mechanism in pydantic-core for validating and serializing structured mappings. It provides a runtime implementation of Python's typing.TypedDict, allowing for strict key-value validation, handling of extra fields, and complex key mapping via aliases.
Core Components
The implementation relies on two primary structures defined in pydantic_core.core_schema:
TypedDictSchema: Defines the overall structure of the mapping, including its fields, how to handle unknown keys, and whether fields are required by default.TypedDictField: Defines the validation and serialization logic for an individual key within the dictionary.
Defining a Basic Schema
A TypedDictSchema is constructed by providing a dictionary of fields. Each field is defined using typed_dict_field, which specifies the CoreSchema to use for that field's value.
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()),
}
)
)
# Validates a dictionary with the specified keys
result = v.validate_python({'name': 'Alice', 'age': 30})
assert result == {'name': 'Alice', 'age': 30}
Field Requirements and Defaults
The requirement of fields is controlled by the total attribute on the TypedDictSchema and the required attribute on individual TypedDictField entries.
Total vs. Optional
total=True(Default): All fields in thefieldsdictionary are required unless specifically marked asrequired=False.total=False: All fields are optional unless specifically marked asrequired=True.
# Using total=False to make fields optional by default
v = SchemaValidator(
core_schema.typed_dict_schema(
total=False,
fields={
'x': core_schema.typed_dict_field(schema=core_schema.int_schema()),
'y': core_schema.typed_dict_field(schema=core_schema.int_schema(), required=True),
},
)
)
# 'x' is optional, but 'y' is required
assert v.validate_python({'y': 1}) == {'y': 1}
Default Values
Fields can have default values by wrapping their schema with with_default_schema. If a field has a default value, it is effectively optional during validation, as the default will be used if the key is missing.
v = SchemaValidator(
core_schema.typed_dict_schema(
fields={
'x': core_schema.typed_dict_field(
schema=core_schema.with_default_schema(schema=core_schema.str_schema(), default='default_val')
),
}
)
)
assert v.validate_python({}) == {'x': 'default_val'}
Constraint: A field cannot be marked as required=True if it also has a default value. Attempting to do so will raise a SchemaError.
# This will raise: SchemaError: Field 'x': a required field cannot have a default value
core_schema.typed_dict_schema(
fields={
'x': core_schema.typed_dict_field(
schema=core_schema.with_default_schema(schema=core_schema.str_schema(), default='pika'),
required=True,
)
}
)
Handling Extra Fields
The extra_behavior attribute determines how the validator handles keys present in the input that are not defined in the fields dictionary.
ignore(Default): Extra fields are silently dropped.forbid: AValidationErroris raised if extra fields are present.allow: Extra fields are included in the output.
Validating Extra Fields
When extra_behavior is set to 'allow', you can use extras_schema to validate the values of these unknown keys.
v = SchemaValidator(
core_schema.typed_dict_schema(
fields={'a': core_schema.typed_dict_field(schema=core_schema.int_schema())},
extra_behavior='allow',
extras_schema=core_schema.str_schema(),
)
)
# 'b' is an extra field, validated as a string
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' results in a SchemaError.
Aliases and Data Extraction
TypedDictField supports validation_alias for mapping input keys to internal field names. This is particularly useful when the input data structure does not match the desired internal representation.
Path Aliases
The validation_alias can be a list representing a path into the input data, allowing for extraction from nested structures or specific list indices.
v = SchemaValidator(
core_schema.typed_dict_schema(
fields={
'target_field': core_schema.typed_dict_field(
validation_alias=['data', 'items', -1],
schema=core_schema.int_schema()
)
}
)
)
# Extracts the last item from the list at 'data' -> 'items'
assert v.validate_python({'data': {'items': [10, 20, 30]}}) == {'target_field': 30}
Serialization
TypedDictSchema also governs how the data is serialized back to a dictionary.
serialization_alias: Defines the key name used in the output dictionary.serialization_exclude: IfTrue, the field is omitted from the serialized output.serialization_exclude_if: A callable that determines exclusion based on the field's value.
v = SchemaValidator(
core_schema.typed_dict_schema(
fields={
'internal_name': core_schema.typed_dict_field(
schema=core_schema.str_schema(),
serialization_alias='external_name'
)
}
)
)
# During serialization, 'internal_name' becomes 'external_name'
The cls and cls_name attributes in TypedDictSchema are used to associate the schema with a specific Python class, which improves error messages and helps the SchemaSerializer identify the correct serialization logic.