Reading — step 1 of 5
Read
Streaming Parser (SAX-style)
json.loads is a tree-building parser: the whole document becomes one in-memory value. For a 20 GB log dump that is a death sentence. A streaming parser never builds the tree — it emits events as it scans, and the caller decides what to keep:
Input: {"name": "alice", "age": 30}
Events: START_OBJECT
KEY name
STRING alice
KEY age
NUMBER 30
END_OBJECT
The idea comes from XML's SAX API; JSON incarnations are ijson (Python), Jackson's streaming API (Java), yajl (C), and serde_json's streaming layer (Rust). What it buys you:
- Bounded memory — gigabytes of input, constant RAM.
- Early termination — found what you were looking for? Stop reading.
- Pipelining — events can feed a downstream consumer before the document ends.
- Validation-only — check well-formedness without materializing anything.
Two API styles exist: push (parser drives, calling your handler callbacks — SAX classic) and pull (you drive, asking for the next event — StAX, Python generators). You'll build pull: in Python a generator function that yields events mid-scan is the whole pattern.
The state you actually need
Streaming does not mean stateless. Two questions demand context the current character cannot answer:
- Is this string a KEY or a STRING value? In an object, strings alternate roles: member name, then member value. Keep a stack of contexts — push when you enter
{or[, pop on close, and track within-object phase. A string isKEYexactly when the top context is an object expecting a member name. - Is a comma legal here? Same stack answers it.
The stack holds one enum per nesting level — bytes, not the document. That is the trick: structure state is cheap; content is what streaming refuses to hold.
Errors cannot be unprinted
A tree parser that fails returns nothing. A streaming parser has already emitted events by the time it hits byte N — and that is a feature (downstream work on valid prefixes has already happened) with a sharp edge: consumers must be ready for a stream that ends in an error instead of clean closes. Your harness prints events as they arrive, so a malformed tail means good events followed by an ERR line. Design consumers for exactly that.
Your exercise
Emit one event per line, spelled exactly: START_OBJECT, END_OBJECT, START_ARRAY, END_ARRAY, KEY <name>, STRING <s>, NUMBER <n>, TRUE, FALSE, NULL. Scalars are whole documents: null prints NULL, 42 prints NUMBER 42, "hi" prints STRING hi. The KEY/STRING distinction is graded: {"name":"alice"} is KEY name then STRING alice — emit STRING name and you fail. The grader-caught streaming subtlety is the hidden test {"x":1,}: the expected output is the three good events START_OBJECT, KEY x, NUMBER 1 and then ERR object key must be a string — the events already printed stay printed (a stream cannot retract), and the error message here is exactly object key must be a string, because this parser discovers a } where the promised member name should be. Raise ValueError("object key must be a string") at that point and let the harness print it.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…