Every API response, cache entry and log line needs the model as plain data again.
Serialization is the reverse of validation. Two methods cover almost every case:
model_dump()returns a dict, recursing into nested models. By default it keeps Python objects such asdatetimeandUUID.model_dump(mode="json")returns a dict with only JSON-compatible values (strings for dates, lists for sets).model_dump_json()returns a JSON string directly from the Rust core; passindent=2for pretty output.
Filtering options shared by both:
include={"id", "name"}orexclude={"password"}; nested sets or dicts select inside sub-modelsexclude_none=Truedrops fields whose value isNoneexclude_defaults=Truedrops fields still equal to their defaultexclude_unset=Truedrops fields the caller never provided, which is exactly what a PATCH endpoint needs: only apply what the client sent
model_copy(update={...}) makes a copy with some fields changed. Note that it does not validate the update; to get validation, dump, change and call model_validate again.
model_fields_set tells you which fields were explicitly set, the information behind exclude_unset.
The v1 methods .dict(), .json() and .copy() still work with deprecation warnings; use the model_ versions in new code.
Example
from datetime import datetime
from pydantic import BaseModel
class Profile(BaseModel):
name: str
bio: str | None = None
theme: str = "light"
joined: datetime
p = Profile(name="Ann", joined=datetime(2024, 1, 2, 3, 4), theme="dark")
print(p.model_dump())
# {'name': 'Ann', 'bio': None, 'theme': 'dark', 'joined': datetime.datetime(2024, 1, 2, 3, 4)}
print(p.model_dump(mode="json", exclude_none=True))
# {'name': 'Ann', 'theme': 'dark', 'joined': '2024-01-02T03:04:00'}
patch = Profile.model_validate({"name": "Ann", "joined": "2024-01-02T03:04"})
print(patch.model_dump(exclude_unset=True, include={"name", "theme"})) # {'name': 'Ann'}
print(p.model_copy(update={"bio": "hi"}).bio) # hiRemember
- model_dump() -> dict, model_dump(mode="json") -> JSON-safe dict, model_dump_json() -> str
- exclude_unset keeps only what the caller provided: ideal for PATCH
- model_copy(update=...) skips validation
Watch out for
- Passing model_dump() output with datetimes to json.dumps; use mode="json" or model_dump_json().