Skip to main content

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=False in typed_dict_schema to make all fields optional by default.
  • Individual Override: Use the required parameter in typed_dict_field to 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 use extras_schema to 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 on extra_behavior.
  • Missing Fields: If total=True (the default), forgetting a field in the input will raise a ValidationError with type=missing.
  • Attribute Access: When using computed_fields or from_attributes=True, ensure the input object actually has the attributes defined in property_name. If the input is a dictionary, computed_field will fail to find the attribute as it uses getattr().