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.
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.