Using Serialization Metadata
During the serialization process, Pydantic provides metadata about the current operation through the SerializationInfo and FieldSerializationInfo protocols. These objects allow custom serializers to adapt their behavior based on the serialization mode (JSON vs. Python), user-provided context, and field-specific details.
Core Metadata Protocols
The metadata is encapsulated in two primary protocols defined in pydantic_core.core_schema:
SerializationInfo: The base protocol used for model-level and general serialization. It contains global state such as the serialization mode and filtering flags.FieldSerializationInfo: Inherits fromSerializationInfoand adds afield_nameproperty. This is used specifically in field-level serializers (e.g., those defined with@field_serializer).
Key Properties
Both protocols provide access to the following properties:
mode: A string indicating the target format, typically'python'or'json'.context: A user-defined dictionary or object passed during the call tomodel_dumpormodel_dump_json.include/exclude: The sets or dictionaries defining which fields should be included or excluded in the current operation.- Filtering Flags: Boolean flags like
exclude_none,exclude_unset, andby_aliasthat reflect the arguments passed to the dump method.
Branching Logic by Mode
One of the most common uses for SerializationInfo is to provide different output formats depending on whether the user is serializing to a Python dictionary or a JSON string.
In pydantic/types.py, the _serialize_secret function uses the mode property to determine if it should return the raw secret object or its string representation:
def _serialize_secret(value: Secret[SecretType], info: core_schema.SerializationInfo) -> str | Secret[SecretType]:
if info.mode == 'json':
return str(value)
else:
return value
This ensures that model_dump() preserves the Secret type wrapper while model_dump_json() produces a plain string.
Accessing Field Context
When using @field_serializer, the info argument is an instance of FieldSerializationInfo. This allows the serializer to know exactly which field it is processing via the field_name property.
As seen in tests/test_serialize.py, this is useful for validation or logic that depends on the field identity:
@field_serializer('f1')
def ser_f1(self, v: Any, info: FieldSerializationInfo) -> Any:
assert info.field_name == 'f1'
return f'{v:,}'
Using Serialization Context
The context property allows you to pass external state into your custom serializers. This is useful for things like localization, unit conversion, or permission-based filtering that isn't stored on the model itself.
# Example of using context in a serializer
def serialize_with_context(value: Any, info: SerializationInfo) -> Any:
unit = info.context.get('target_unit', 'metric')
if unit == 'imperial':
return convert_to_imperial(value)
return value
Propagating Info in Wrap Serializers
When using a "wrap" serializer (defined with mode='wrap'), you receive a handler function. To ensure that the rest of the serialization process respects the current state (like filtering and mode), you should pass the info object to the handler.
In pydantic/functional_serializers.py, the WrapSerializer implementation demonstrates how the info object is passed through to the next handler:
def convert_to_utc(value: Any, handler, info) -> dict[str, datetime]:
# The handler is called with both the value and the info object
partial_result = handler(value, info)
if info.mode == 'json':
return {
k: datetime.fromisoformat(v).astimezone(timezone.utc)
for k, v in partial_result.items()
}
return {k: v.astimezone(timezone.utc) for k, v in partial_result.items()}
By passing info to handler(value, info), you ensure that nested models or fields correctly receive the same include/exclude rules and context that were originally requested.