Day 14 · ~12m

Custom Serializers

Using @field_serializer and @computed_field to customize model output.

🧑‍💻

Sometimes I need the output format to be different from the internal representation. Like storing a datetime object but serializing it as a string. How do I customize that?

👩‍🏫

With @field_serializer. It's like @field_validator but for output instead of input:

from pydantic import BaseModel, field_serializer
from datetime import datetime

class Event(BaseModel):
    title: str
    starts_at: datetime

    @field_serializer("starts_at")
    def serialize_datetime(self, v: datetime) -> str:
        return v.strftime("%Y-%m-%d %H:%M")

event = Event(title="Launch", starts_at=datetime(2026, 3, 15, 14, 30))
print(event.model_dump())
# {'title': 'Launch', 'starts_at': '2026-03-15 14:30'}

The serializer runs whenever you call model_dump() or model_dump_json(). Internally, starts_at is a full datetime object. In the output, it's a formatted string.

🧑‍💻

That's for transforming existing fields. What about fields that are computed from other fields, like a full name from first and last name?

👩‍🏫

Use @computed_field. It creates a field that exists only in the output — it's not part of the input schema:

from pydantic import BaseModel, computed_field

class Person(BaseModel):
    first_name: str
    last_name: str

    @computed_field
    @property
    def full_name(self) -> str:
        return f"{self.first_name} {self.last_name}"

p = Person(first_name="Alice", last_name="Smith")
print(p.full_name)       # "Alice Smith"
print(p.model_dump())
# {'first_name': 'Alice', 'last_name': 'Smith', 'full_name': 'Alice Smith'}

The @computed_field decorator combined with @property means: include this in serialization output, but don't accept it as input. It's calculated from the real fields.

🧑‍💻

Can I use @computed_field for more complex calculations? Like computing a discount price?

👩‍🏫

Absolutely. Computed fields can use any Python logic:

class Product(BaseModel):
    name: str
    price: float
    discount_percent: float = 0.0

    @computed_field
    @property
    def final_price(self) -> float:
        return round(self.price * (1 - self.discount_percent / 100), 2)

p = Product(name="Widget", price=29.99, discount_percent=10)
print(p.final_price)     # 26.99
print(p.model_dump())
# {'name': 'Widget', 'price': 29.99, 'discount_percent': 10.0, 'final_price': 26.99}
🧑‍💻

Can I have a @field_serializer that formats numbers differently? Like showing currency?

👩‍🏫

Yes. Serializers give you full control over output format:

class Invoice(BaseModel):
    item: str
    amount: float
    currency: str = "USD"

    @field_serializer("amount")
    def format_amount(self, v: float) -> str:
        return f"{v:,.2f}"

inv = Invoice(item="Consulting", amount=15000.5)
print(inv.model_dump())
# {'item': 'Consulting', 'amount': '15,000.50', 'currency': 'USD'}

The key pattern: validators normalize on the way in, serializers format on the way out. Your model is the clean middle layer — typed, validated, and easy to work with in Python code.

Practice your skills

Sign up to write and run code in this lesson.

Already have an account? Sign in