Real data contains timestamps, money and your own classes, which json refuses out of the box.
When the encoder meets an object it doesn't know, it calls the function passed as default. The function must return something JSON-serialisable (a str, number, list, dict...) or raise TypeError for types it doesn't handle either.
def to_json(o):
if isinstance(o, datetime):
return o.isoformat()
raise TypeError(f"{type(o).__name__} is not JSON serializable")Typical conversions:
datetime/date→o.isoformat()(an ISO 8601 string)Decimal→str(o)to keep exact digits (orfloatif precision doesn't matter)set/frozenset→sorted(o)for stable outputUUID,Path→str(o)- dataclasses →
dataclasses.asdict(o)(recursive) Enum→o.value
The return value is encoded recursively, so default can return a dict containing other custom objects and they'll pass through default too.
default is only called for unsupported types. You can't use it to change how ints, strings or dicts are written, and subclasses of dict, list, int, str and float are encoded as their base type without calling it.
Decoding doesn't automatically reverse this: the datetime comes back as a string. Converting back is the job of the decoding side (object hooks or a schema library).
Example
import json
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from decimal import Decimal
@dataclass
class Order:
id: int
total: Decimal
created: datetime
tags: set
def to_json(o):
if isinstance(o, datetime):
return o.isoformat()
if isinstance(o, Decimal):
return str(o)
if isinstance(o, set):
return sorted(o)
raise TypeError(f"{type(o).__name__} is not JSON serializable")
o = Order(1, Decimal("19.90"), datetime(2025, 3, 1, 12, tzinfo=timezone.utc), {"b", "a"})
print(json.dumps(asdict(o), default=to_json))
# {"id": 1, "total": "19.90", "created": "2025-03-01T12:00:00+00:00", "tags": ["a", "b"]}Remember
default=fnis called for each object json can't encode; return a serialisable value- Raise TypeError for types you don't handle
- Decoding returns the strings; convert them back explicitly
Watch out for
- Returning the object unchanged from
default, which makes the encoder fail with a circular reference error.