Skip to main content
Functional serializers allow you to customize how fields and models are serialized using decorators and annotated metadata classes.

Decorators

field_serializer

def field_serializer(
    field: str,
    /,
    *fields: str,
    mode: Literal['plain', 'wrap'] = 'plain',
    return_type: Any = PydanticUndefined,
    when_used: WhenUsed = 'always',
    check_fields: bool | None = None,
) -> Callable[[FieldSerializer], FieldSerializer]
Decorate methods on a model class to customize serialization for one or more fields.
field
str
required
The first field name to serialize. At least one field name is required.
*fields
str
Additional field names to serialize with the same serializer function.
mode
Literal['plain', 'wrap']
default:"'plain'"
The serialization mode:
  • 'plain': The function replaces the default serialization logic
  • 'wrap': The function wraps the default serialization, receives a handler to call default logic
return_type
Any
default:"PydanticUndefined"
Optional return type for the function. If omitted, it will be inferred from the type annotation.
when_used
WhenUsed
default:"'always'"
Determines when the serializer is used:
  • 'always': Always use this serializer
  • 'unless-none': Use unless the value is None
  • 'json': Only use in JSON serialization mode
  • 'json-unless-none': Use in JSON mode unless the value is None
check_fields
bool | None
default:"None"
Whether to check that the specified fields actually exist on the model. When None, uses the model’s configuration.

Serializer Function Signatures

Field serializers support four different signatures: Plain mode (instance method):
@field_serializer('field_name')
def serialize_field(self, value: Any) -> Any:
    return value

# With info argument:
@field_serializer('field_name')
def serialize_field(self, value: Any, info: FieldSerializationInfo) -> Any:
    return value
Wrap mode (instance method):
@field_serializer('field_name', mode='wrap')
def serialize_field(
    self,
    value: Any,
    nxt: SerializerFunctionWrapHandler,
) -> Any:
    # Call nxt(value) for default serialization
    return nxt(value)

# With info argument:
@field_serializer('field_name', mode='wrap')
def serialize_field(
    self,
    value: Any,
    nxt: SerializerFunctionWrapHandler,
    info: FieldSerializationInfo,
) -> Any:
    return nxt(value)
Static/class methods:
# Without self (static-like):
@field_serializer('field_name')
@staticmethod
def serialize_field(value: Any, info: SerializationInfo) -> Any:
    return value

# Wrap mode without self:
@field_serializer('field_name', mode='wrap')
@staticmethod
def serialize_field(
    value: Any,
    nxt: SerializerFunctionWrapHandler,
    info: SerializationInfo,
) -> Any:
    return nxt(value)

Example

from pydantic import BaseModel, field_serializer

class StudentModel(BaseModel):
    name: str = 'Jane'
    courses: set[str]

    @field_serializer('courses', when_used='json')
    def serialize_courses_in_order(self, courses: set[str]):
        return sorted(courses)

student = StudentModel(courses={'Math', 'Chemistry', 'English'})
print(student.model_dump_json())
#> {"name":"Jane","courses":["Chemistry","English","Math"]}

model_serializer

def model_serializer(
    f: ModelSerializer | None = None,
    /,
    *,
    mode: Literal['plain', 'wrap'] = 'plain',
    when_used: WhenUsed = 'always',
    return_type: Any = PydanticUndefined,
) -> ModelSerializer | Callable[[ModelSerializer], ModelSerializer]
Decorate a model method to customize serialization for the entire model.
f
ModelSerializer | None
default:"None"
The function to be decorated. When None, returns a decorator function.
mode
Literal['plain', 'wrap']
default:"'plain'"
The serialization mode:
  • 'plain': The function replaces the default serialization logic
  • 'wrap': The function wraps the default serialization, receives a handler to call default logic
when_used
WhenUsed
default:"'always'"
Determines when the serializer is used:
  • 'always': Always use this serializer
  • 'unless-none': Use unless the value is None
  • 'json': Only use in JSON serialization mode
  • 'json-unless-none': Use in JSON mode unless the value is None
return_type
Any
default:"PydanticUndefined"
Optional return type for the function. If omitted, it will be inferred from the type annotation.

Serializer Function Signatures

Plain mode:
# Without info:
@model_serializer()
def serialize_model(self) -> dict[str, Any]:
    return {'field': self.field}

# With info:
@model_serializer()
def serialize_model(self, info: SerializationInfo) -> dict[str, Any]:
    return {'field': self.field}
Wrap mode:
# Without info:
@model_serializer(mode='wrap')
def serialize_model(self, nxt: SerializerFunctionWrapHandler) -> dict[str, Any]:
    # Call nxt(self) for default serialization
    return nxt(self)

# With info:
@model_serializer(mode='wrap')
def serialize_model(
    self,
    nxt: SerializerFunctionWrapHandler,
    info: SerializationInfo,
) -> dict[str, Any]:
    return nxt(self)
The decorator can be used without parentheses @model_serializer or with parentheses @model_serializer() for plain mode with default parameters.

Example

from typing import Literal
from pydantic import BaseModel, model_serializer

class TemperatureModel(BaseModel):
    unit: Literal['C', 'F']
    value: int

    @model_serializer()
    def serialize_model(self):
        if self.unit == 'F':
            return {'unit': 'C', 'value': int((self.value - 32) / 1.8)}
        return {'unit': self.unit, 'value': self.value}

temperature = TemperatureModel(unit='F', value=212)
print(temperature.model_dump())
#> {'unit': 'C', 'value': 100}

Serializer Classes

Serializer classes are used with typing.Annotated to apply serialization logic to type annotations.

PlainSerializer

@dataclass
class PlainSerializer:
    func: SerializerFunction
    return_type: Any = PydanticUndefined
    when_used: WhenUsed = 'always'
Use a function to modify the output of serialization, replacing the default serialization logic.
func
SerializerFunction
required
The serializer function. Signature: (value: Any) -> Any or (value: Any, info: SerializationInfo) -> Any.
return_type
Any
default:"PydanticUndefined"
The return type for the function. If omitted, it will be inferred from the type annotation.
when_used
WhenUsed
default:"'always'"
Determines when this serializer should be used:
  • 'always': Always use this serializer
  • 'unless-none': Use unless the value is None
  • 'json': Only use in JSON serialization mode
  • 'json-unless-none': Use in JSON mode unless the value is None

Example

from typing import Annotated
from pydantic import BaseModel, PlainSerializer

CustomStr = Annotated[
    list, PlainSerializer(lambda x: ' '.join(x), return_type=str)
]

class StudentModel(BaseModel):
    courses: CustomStr

student = StudentModel(courses=['Math', 'Chemistry', 'English'])
print(student.model_dump())
#> {'courses': 'Math Chemistry English'}

WrapSerializer

@dataclass
class WrapSerializer:
    func: WrapSerializerFunction
    return_type: Any = PydanticUndefined
    when_used: WhenUsed = 'always'
Wrap the default serialization logic with a custom function that can modify the input before and/or the output after standard serialization.
func
WrapSerializerFunction
required
The wrap serializer function. Must accept the value, a handler function, and optionally a SerializationInfo object.
return_type
Any
default:"PydanticUndefined"
The return type for the function. If omitted, it will be inferred from the type annotation.
when_used
WhenUsed
default:"'always'"
Determines when this serializer should be used:
  • 'always': Always use this serializer
  • 'unless-none': Use unless the value is None
  • 'json': Only use in JSON serialization mode
  • 'json-unless-none': Use in JSON mode unless the value is None

Function Signatures

# Without info:
def wrap_serializer(
    value: Any,
    handler: SerializerFunctionWrapHandler
) -> Any:
    # Transform value before default serialization
    result = handler(value)
    # Transform result after default serialization
    return result

# With info:
def wrap_serializer(
    value: Any,
    handler: SerializerFunctionWrapHandler,
    info: SerializationInfo
) -> Any:
    partial_result = handler(value, info)
    return partial_result

Example

from datetime import datetime, timezone
from typing import Annotated, Any
from pydantic import BaseModel, WrapSerializer

class EventDatetime(BaseModel):
    start: datetime
    end: datetime

def convert_to_utc(value: Any, handler, info) -> dict[str, datetime]:
    # Use handler to serialize the value first
    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()}

UTCEventDatetime = Annotated[EventDatetime, WrapSerializer(convert_to_utc)]

class EventModel(BaseModel):
    event_datetime: UTCEventDatetime

dt = EventDatetime(
    start='2024-01-01T07:00:00-08:00',
    end='2024-01-03T20:00:00+06:00'
)
event = EventModel(event_datetime=dt)
print(event.model_dump())
# {'event_datetime': {
#     'start': datetime.datetime(2024, 1, 1, 15, 0, tzinfo=datetime.timezone.utc),
#     'end': datetime.datetime(2024, 1, 3, 14, 0, tzinfo=datetime.timezone.utc),
# }}

print(event.model_dump_json())
#> {"event_datetime":{"start":"2024-01-01T15:00:00Z","end":"2024-01-03T14:00:00Z"}}

Additional Classes

SerializeAsAny

class SerializeAsAny:
    @classmethod
    def __class_getitem__(cls, item: Any) -> Any
Annotation used to mark a type as having duck-typing serialization behavior. This allows serialization to use the actual runtime type rather than the declared type. See usage documentation for more details.

Example

from pydantic import BaseModel, SerializeAsAny

class Pet(BaseModel):
    name: str

class Dog(Pet):
    breed: str

class Model(BaseModel):
    pet: SerializeAsAny[Pet]

dog = Dog(name='Fido', breed='Labrador')
model = Model(pet=dog)

print(model.model_dump())
# Includes 'breed' field even though type is Pet
#> {'pet': {'name': 'Fido', 'breed': 'Labrador'}}

Build docs developers (and LLMs) love