Cheatsheet for the output side.
model_dump
user.model_dump() # Python types
user.model_dump(mode="json") # JSON-safe types (datetime → str, etc.)
user.model_dump_json() # JSON string (Rust serializer; fast)
Filter
user.model_dump(exclude={"created_at"})
user.model_dump(include={"id", "email"})
user.model_dump(exclude={"profile": {"address"}}) # nested
user.model_dump(exclude_none=True)
user.model_dump(exclude_unset=True)
user.model_dump(exclude_defaults=True)
user.model_dump(by_alias=True)
field_serializer
from pydantic import field_serializer
from datetime import datetime
class Event(BaseModel):
when: datetime
@field_serializer("when")
def serialize_when(self, v: datetime, _info) -> str:
return v.isoformat()
@field_serializer("when", when_used="json")
def to_iso_json(self, v, _info):
return v.isoformat()
when_used: "always", "json", "json-unless-none", "unless-none".
model_serializer
from pydantic import model_serializer
class User(BaseModel):
id: int
email: str
@model_serializer
def serialize(self) -> dict:
return {"user_id": self.id, "user_email": self.email}
Override the whole model.
computed_field
from pydantic import computed_field
class User(BaseModel):
first: str
last: str
@computed_field
@property
def full_name(self) -> str:
return f"{self.first} {self.last}"
Serialized; not accepted on input.
Custom serialization via Annotated
from typing import Annotated
from pydantic import PlainSerializer
UnixTimestamp = Annotated[
datetime,
PlainSerializer(lambda dt: int(dt.timestamp()), return_type=int, when_used="json"),
]
class Event(BaseModel):
when: UnixTimestamp
Reusable.
WrapSerializer
from pydantic import WrapSerializer
def conditional(v, handler, info):
if info.context and info.context.get("verbose"):
return {"value": handler(v), "ts": time.time()}
return handler(v)
class M(BaseModel):
name: Annotated[str, WrapSerializer(conditional)]
Excluded fields
class User(BaseModel):
id: int
email: str
password_hash: str = Field(..., exclude=True)
exclude=True on Field — permanently excluded from serialization.
by_alias
class User(BaseModel):
full_name: str = Field(serialization_alias="fullName")
User(full_name="Alice").model_dump(by_alias=True)
# {"fullName": "Alice"}
Round-trip
user = User(id=1, email="[email protected]")
js = user.model_dump_json()
restored = User.model_validate_json(js)
Bytes / JSON directly (fast)
user.__pydantic_serializer__.to_json(user) # bytes directly (Rust)
For protocols where you want bytes, not str.
Pydantic vs json.dumps
# Slower
json.dumps(user.model_dump())
# Faster (Rust serializer)
user.model_dump_json()
Use the built-in for hot paths.
Nested
class Group(BaseModel):
members: list[User]
g.model_dump()
# {"members": [{...}, {...}]}
Auto-recursive.
Per-context output
class User(BaseModel):
id: int
email: str
# For different audiences, use distinct schemas:
class UserPublic(BaseModel):
id: int
class UserInternal(BaseModel):
id: int
email: str
Common mistakes
- Returning ORM model directly in FastAPI without
response_model— leaks fields. dict()(v1) instead ofmodel_dump().json.dumps(model.model_dump())for hot path — usemodel_dump_json().mode="json"not set when datetime appears —Object of type datetime is not JSON serializable.
Read this next
If you want my pydantic + orjson FastAPI starter, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .