Structured Data with TypedDict
To define fixed-key dictionary structures in this codebase, you use the typed_dict_schema and typed_dict_field functions from pydantic_core.core_schema. These allow you to specify exact keys, their types, requirement status, and how they should be mapped during validation and serialization.
from pydantic_core import SchemaValidator, SchemaSerializer, core_schema
# Define a schema for a User dictionary
schema = core_schema.typed_dict_schema(
fields={
'username': core_schema.typed_dict_field(core_schema.str_schema()),
'age': core_schema.typed_dict_field(core_schema.int_schema(), required=False),
}
)
# Validation
v = SchemaValidator(schema)
assert v.validate_python({'username': 'johndoe', 'age': 30}) == {'username': 'johndoe', 'age': 30}
# Serialization
s = SchemaSerializer(schema)
assert s.to_python({'username': 'johndoe'}) == {'username': 'johndoe'}
Defining Fields and Requirements
The TypedDictSchema manages a collection of TypedDictField objects. By default, a TypedDictSchema is "total", meaning all fields are required unless specified otherwise.
- Total Schema: Set
total=Falseintyped_dict_schemato make all fields optional by default. - Individual Override: Use the
requiredparameter intyped_dict_fieldto override the schema's default behavior for a specific field.
schema = core_schema.typed_dict_schema(
fields={
# Explicitly required even if total=False
'id': core_schema.typed_dict_field(core_schema.int_schema(), required=True),
# Optional field
'email': core_schema.typed_dict_field(core_schema.str_schema(), required=False),
},
total=False
)
Mapping Data with Aliases
You can map internal field names to different keys in the input or output data using validation_alias and serialization_alias.
validation_alias: Used to find the field in the input data. It can be a string or a list of paths.serialization_alias: Used as the key when the data is serialized.
schema = core_schema.typed_dict_schema(
fields={
'first_name': core_schema.typed_dict_field(
core_schema.str_schema(),
validation_alias='FName',
serialization_alias='firstName'
)
}
)
v = SchemaValidator(schema)
# Validates from 'FName'
result = v.validate_python({'FName': 'Jane'})
assert result == {'first_name': 'Jane'}
s = SchemaSerializer(schema)
# Serializes to 'firstName' when by_alias=True
assert s.to_python(result, by_alias=True) == {'firstName': 'Jane'}
Handling Extra Fields
The extra_behavior parameter determines how the validator handles keys that are not defined in the fields dictionary.
'ignore'(default): Extra fields are silently dropped.'forbid': Validation fails if extra fields are present.'allow': Extra fields are preserved. You can optionally useextras_schemato validate these extra values.
schema = 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() # Extra values must be strings
)
v = SchemaValidator(schema)
assert v.validate_python({'a': 1, 'b': 'extra'}) == {'a': 1, 'b': 'extra'}
Adding Computed Fields
ComputedField allows you to include derived data during serialization. These fields are not part of the input validation but are calculated from the object or dictionary being serialized.
Note that computed fields typically expect the input to be an object with attributes (using getattr) rather than a plain dictionary.
class Rectangle:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
@property
def area(self) -> int:
return self.width * self.height
schema = core_schema.typed_dict_schema(
fields={
'width': core_schema.typed_dict_field(core_schema.int_schema()),
'height': core_schema.typed_dict_field(core_schema.int_schema()),
},
computed_fields=[
core_schema.computed_field('area', core_schema.int_schema(), alias='total_area')
]
)
s = SchemaSerializer(schema)
rect = Rectangle(4, 5)
# Computed field 'area' is included in output
assert s.to_python(rect, by_alias=True) == {'width': 4, 'height': 5, 'total_area': 20}
Troubleshooting
- Computed Fields in Validation: Computed fields are strictly for serialization. If you include them in the input dictionary during
validate_python, they will be treated as extra fields and either ignored, forbidden, or allowed based onextra_behavior. - Missing Fields: If
total=True(the default), forgetting a field in the input will raise aValidationErrorwithtype=missing. - Attribute Access: When using
computed_fieldsorfrom_attributes=True, ensure the input object actually has the attributes defined inproperty_name. If the input is a dictionary,computed_fieldwill fail to find the attribute as it usesgetattr().