json & Serialization
The json module encodes Python objects to JSON text and parses JSON into dict/list scalars. Customize with default encoders and object_hook for round-trips - for APIs prefer Pydantic 2 at boundaries.
Recipe
import json
from datetime import datetime, timezone
def default(obj):
if isinstance(obj, datetime):
return obj.astimezone(timezone.utc).isoformat()
raise TypeError(type(obj))
payload = json.dumps({"at": datetime.now(timezone.utc)}, default=default)When to reach for this:
- Config files and CLI JSON output
- Simple REST without Pydantic overhead
json.loadson trusted small payloads- NDJSON log lines
- Interop with JavaScript clients
Working Example
import json
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from pathlib import Path
@dataclass
class Event:
name: str
at: datetime
def encode_event(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(type(obj))
def save_events(path: Path, events: list[Event]) -> None:
raw = [asdict(e) for e in events]
path.write_text(json.dumps(raw, default=encode_event, indent=2), encoding="utf-8")
def load_events(path: Path) -> list[dict]:
return json.loads(path.read_text(encoding="utf-8"))
events = [Event("login", datetime.now(timezone.utc))]
out = Path("events.json")
save_events(out, events)
print(load_events(out))
out.unlink(missing_ok=True)What this demonstrates:
- Dataclass -> dict -> JSON pipeline
- Custom
defaultfor non-JSON-native types - Explicit utf-8 file encoding
- Parse returns dicts - validate before use
Deep Dive
JSON Types
| Python | JSON |
|---|---|
| dict | object |
| list | array |
| str, int, float, bool, None | scalar |
Performance
orjsonPyPI faster for hot pathsjson.dumpsfine for CLI and config
Gotchas
- float precision - JSON numbers are IEEE doubles. Fix: Decimal as str if money.
- Key type str only - dict keys coerced to str in JSON. Fix: normalize on load.
- Huge file loads - memory spike. Fix:
ijsonstreaming or line-delimited. - Untrusted input depth - DoS via nested objects. Fix: limit size, use parser with limits.
- datetime naive in JSON - ambiguous. Fix: always UTC ISO with offset.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Pydantic | API models validation | Tiny internal dump |
| msgpack | Binary compact IPC | Human-readable need |
| yaml | Human config | Strict JSON API |
FAQs
loads vs load?
loads string; load file object - prefer Path read_text + loads for clarity.
sort_keys=True?
Stable diffs for config - costs sort overhead.
ensure_ascii=False?
Emit UTF-8 characters in JSON strings - usually desired in 3.x.
Decimal serialization?
Convert to str in default - never float for money.
UUID?
str(uuid) in default encoder.
FastAPI response?
Framework jsonable_encoder - pydantic models preferred.
NDJSON?
One json.dumps per line written to file or stdout.
schema validation?
jsonschema PyPI or Pydantic after loads.
pickle vs json?
Never pickle untrusted network data; json for interchange.
indent in prod?
No - pretty print for humans only; compact in APIs.
Related
- datetime, zoneinfo & time - encode datetimes
- Pydantic basics - validated models
- pathlib & os - config paths
- Standard Library Best Practices - serialization rules
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.