Custom Field Serialization with @field_serializer
@field_serializer is an exit-only adapter for one field: it reshapes data leaving the Pydantic model without changing internals. Use it to format decimals, mask secrets, or tweak datetimes for FastAPI JSON. Never use it for validation; it only runs on output.
WHY IT EXISTS: Pydantic models often store data in types that are correct for Python but awkward for JSON consumers. A Decimal prevents floating point errors in business logic, yet APIs usually expect currency as strings. Datetime objects are native to Python, but clients may need Unix timestamps or custom ISO formats. Before Pydantic v2, you had to rely on json_encoders, which were global, hard to discover, and divorced from the field they affected. The field_serializer decorator was introduced to give per-field, co-located, explicit control over what the outside world sees while keeping the internal representation untouched.
THE MENTAL MODEL: Think of it as a one-way exit visa checkpoint for a single piece of luggage. The contents inside your model stay exactly the same, but right before the data crosses the border into JSON land, a customs officer opens one specific bag and repackages it according to foreign rules. The officer does not care what happens when luggage enters the country, only when it leaves.
HOW IT WORKS: You define a method on your Pydantic model and decorate it with field_serializer, passing the field name as a string. The method receives the field's current Python value as its first argument, and you return whatever JSON-friendly representation you need. Under the hood, Pydantic v2 calls this hook during model_dump or when FastAPI generates a response. It does not mutate the model instance. You can also accept an info argument containing metadata about the serialization context, though the value alone is enough for most cases. The decorator accepts mode=plain or mode=wrap for advanced composition, but the default plain mode is what most engineers reach for.
WHEN TO USE IT: Reach for field_serializer when you need presentation-layer formatting that should not leak into your domain model. Three common signals: first, you are converting rich Python types like Decimal, UUID, or datetime into strings or integers for the wire format. Second, you are masking sensitive fields such as replacing a full credit card number with last four digits only in API responses. Third, you are deriving a display value from an internal code, like turning an internal status enum into a human readable label without storing that redundancy in your database.
WHEN NOT TO USE IT: Do not use it for input validation or inbound data coercion. If an API client sends a string and you need to parse it into a Decimal during model instantiation, field_serializer will not help because it only runs on the way out. For that, use field_validator or model_validator. Also avoid it when you need to transform every field of a type globally; a custom type with a get_pydantic_core_schema method or json_encoders is cleaner. Finally, if you need to reshape the entire model at once rather than one field, use model_serializer instead.
ONE CANONICAL EXAMPLE: Imagine a Payment model with a balance field typed as Decimal. Your finance team needs the Python object to retain Decimal for exact arithmetic, but your frontend expects a string like 99.99 to avoid JavaScript floating point issues. You add a method decorated with field_serializer, passing the string balance, that receives the Decimal value and returns a formatted string with two decimal places. When you call model_dump_json, the output contains the formatted string. When you load the same JSON back into a Payment instance, the standard Decimal parser handles the inbound string, and your serializer remains blissfully uninvolved. This separation keeps your domain model honest and your API contract friendly.
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.