JSON Round-Trips with the json Module
JSON Round-Trips with the json Module
import json
from dataclasses import dataclass, asdict
@dataclass
class Order:
id: str
qty: int
order = Order("A-1", 3)
text = json.dumps(asdict(order), indent=2, sort_keys=True, ensure_ascii=False)
same = json.loads(text, object_hook=lambda d: Order(**d)) # or explicit parsing
Options that matter in production:
ensure_ascii=Falsekeeps UTF-8 readable (default escapes everything)indent=2for humans; omit for compact wire payloadsdefault=...handles objects json does not know (datetime →isoformat())json.loads(s, parse_float=Decimal)preserves precision for money- Keys must be strings (int keys coerce; tuples and objects raise
TypeError)
Dataclasses as the Serialization Layer
Dataclasses as the Serialization Layer
from dataclasses import dataclass, field, asdict, astuple, replace
@dataclass
class Line:
sku: str
tags: list[str] = field(default_factory=list) # never default=[]
d = asdict(line) # deep dict — safe for nested dataclasses
t = astuple(line) # tuple form when position matters
new = replace(line, sku="B-2") # copy with one field changed (works on frozen too)
Why default_factory: a shared tags=[] default would be one list object reused by every instance — mutations leak across instances and across tests. asdict recurses through nested dataclasses, so one call produces the whole tree ready for json.dumps; non-dataclass values inside (datetime, Decimal) still need a custom default encoder.
YAML, Pickle, and Binary Formats
YAML, Pickle, and Binary Formats
YAML (PyYAML): yaml.safe_load accepts only plain data — lists, dicts, scalars. Never yaml.load without a restricted SafeLoader: full load can construct arbitrary Python objects and execute code from a document.
Pickle: fastest way to round-trip Python objects, but never on untrusted bytes — a payload can run code on unpickle. It is also version-fragile across Python releases and useless to non-Python clients.
Binary options: MessagePack (JSON-shaped, compact, schema-less), Protobuf/Avro (schema + codegen, evolution-friendly) — choose these when payload size or cross-language contracts dominate.
Strict JSON gotchas: NaN/Infinity are accepted by Python by default (allow_nan=False rejects them), and JSON has no datetime type — timestamps must be strings or epoch numbers.
Quiz
1. Why use field(default_factory=list) instead of tags=[] on a dataclass?
2. What is the danger of yaml.load(user_input)?
3. Why never unpickle untrusted data?
Flashcards
Question
json.dumps default=
Click to reveal answer
Answer
Hook converting unknown objects (datetime, Decimal) into serializable values.
Question
asdict vs astuple
Click to reveal answer
Answer
asdict → nested dict for JSON; astuple → positional tuple; both recurse dataclasses.
Question
yaml.safe_load
Click to reveal answer
Answer
Parses plain data only; the safe alternative to the code-executing default load.
Question
Pickle trust rule
Click to reveal answer
Answer
Deserializing pickle = executing code; only use on data you produced.
Revision Notes
Key Takeaways
- 1. json + dataclasses.asdict covers most serialization with explicit hooks for edge types
- 2. Mutable dataclass defaults require default_factory to avoid shared state
- 3. Match the format to trust and audience: safe YAML for config, pickle only for self-produced caches
Interview Tips
- • Name the shared-mutable-default bug and show the one-line factory fix
- • Explain why JSON beats pickle for cross-service interchange, including the security argument
Cheat Sheet
json.dumps(obj, indent=2, ensure_ascii=False, default=f); asdict()/replace(); yaml.safe_load; pickle only trusted; allow_nan=False for strict JSON.