Skip to main content

Specialized Serialization Schemas

Specialized serialization schemas in pydantic-core provide built-in mechanisms to transform data during the serialization process. Unlike custom functional serializers, these schemas leverage optimized internal logic to handle common tasks like string formatting, type conversion to strings, and model-specific serialization.

These schemas are typically defined within the serialization key of a CoreSchema, such as any_schema or int_schema.

String Formatting with FormatSerSchema

The FormatSerSchema (created via core_schema.format_ser_schema) applies Python's standard format() logic to a value during serialization. This is particularly useful for controlling the precision of floating-point numbers or padding strings in the output.

Implementation Details

The schema requires a formatting_string, which follows the standard Python Format Specification Mini-Language.

from pydantic_core import SchemaSerializer, core_schema

# Define a schema that formats floats to 4 decimal places during JSON serialization
schema = core_schema.any_schema(
serialization=core_schema.format_ser_schema('0.4f')
)
serializer = SchemaSerializer(schema)

# In JSON mode, the formatting is applied
assert serializer.to_json(42.123456) == b'"42.1235"'

# By default (when_used='json-unless-none'), it is NOT applied in Python mode
assert serializer.to_python(42.123456) == 42.123456

If the value being serialized is incompatible with the formatting_string (for example, applying an integer format code to a string), pydantic-core raises a PydanticSerializationError.

String Conversion with ToStringSerSchema

The ToStringSerSchema (created via core_schema.to_string_ser_schema) forces a value to be converted to a string using Python's str() function. This is frequently used for types that do not have a native JSON representation, such as fractions.Fraction, uuid.UUID, or complex URL objects.

In the pydantic codebase, this is used in pydantic/networks.py to ensure that specialized URL and IP address types are correctly serialized as strings.

from pydantic_core import SchemaSerializer, core_schema

# Force string conversion in all serialization modes
schema = core_schema.any_schema(
serialization=core_schema.to_string_ser_schema(when_used='always')
)
serializer = SchemaSerializer(schema)

assert serializer.to_python(123) == '123'
assert serializer.to_json(123) == b'"123"'

The when_used Parameter

Both FormatSerSchema and ToStringSerSchema support the when_used parameter, which controls the conditions under which the transformation is applied. This parameter is of type WhenUsed, defined in pydantic_core/core_schema.py as:

WhenUsed = Literal['always', 'unless-none', 'json', 'json-unless-none']

ValueDescription
'always'Transformation is applied in both to_python and to_json calls.
'unless-none'Applied in both modes, unless the value is None.
'json'Applied only during to_json serialization.
'json-unless-none'(Default) Applied only during to_json and only if the value is not None.

Model Serialization with ModelSerSchema

The ModelSerSchema is a specialized schema used to define how models (Pydantic models or dataclasses) should be serialized. It maps a specific Python class to a CoreSchema that describes its fields.

While Pydantic usually generates this automatically, it can be manually configured using core_schema.model_ser_schema. It requires:

  1. cls: The Python class the schema is associated with.
  2. schema: A CoreSchema (usually a model_fields_schema) defining how to serialize the object's attributes.

Type Safety and Warnings

If the object passed to the serializer is not an instance of the specified cls, pydantic-core will issue a UserWarning but will still attempt to serialize the object using the provided schema if the attributes match.

Example usage for a dataclass (adapted from pydantic-core/tests/serializers/test_model.py):

from pydantic_core import core_schema

class MyData:
def __init__(self, a: int):
self.a = a

schema = core_schema.model_schema(
MyData,
core_schema.model_fields_schema({
'a': core_schema.model_field(core_schema.int_schema())
}),
serialization=core_schema.model_ser_schema(
MyData,
core_schema.model_fields_schema({
'a': core_schema.model_field(core_schema.int_schema())
})
)
)

This structure ensures that the serialization logic is tightly bound to the expected class structure while providing fallback behavior for compatible objects.