Rung 3 (M4) — Recursive-Descent JSON Parser¶
Nav: ← Rung 2 · Rung 4 → · Source: Phase 1 · P1.2
Rung 3 is the artifact that flips the perception of you from “knows the language” to “can build language-level things.” A JSON parser is one of the few small, complete engineering problems that is universally understood, has a specification of manageable size, and cannot be faked with a library import. If you ship this, you have written a recursive-descent parser in C++ from scratch — which is a stronger signal than most 3-year C++ engineers can offer.
You will not compete with nlohmann/json or simdjson. That is not the point. The point is to demonstrate that when you use those libraries in your day job, you understand what is happening inside them.
What It Is¶
A public GitHub repo named mini-json-cpp containing:
A header-only C++20 JSON parser:
mini_json::parse(std::string_view) -> Result<Value, ParseError>.A
Valuetype that models the JSON grammar: null, bool, number (double), string, array, object. Usestd::variantor a tagged union — both are fine, but pick one and defend it in the README.A serializer:
mini_json::dump(const Value&, int indent = 0) -> std::string.Recursive-descent parser (no lexer generator, no PEG library, no dependencies).
Precise error reporting: on parse failure, return
ParseErrorcontainingline,col,message, and a short snippet of the offending region.Zero external dependencies beyond the standard library.
Ten test JSON files under
tests/data/, spanning: empty, deeply nested, unicode escapes, edge-case numbers (1e10, -0, 1.7e308), invalid syntax (with expectedline:colin the assertion), the RFC 8259 conformance samples.Fuzz harness (libFuzzer) that runs random byte inputs against
parseand asserts no crash / no UB. This is an optional stretch but strongly recommended — see below.
Why It Matters (Employer Signal)¶
One line: “Can implement a real parser, not just use one.”
Parsing is the most common study coding topic that is not LeetCode. Every compiler, every DSL, every config-file library, every protocol implementation is built on parsing. Showing that you have written one — with proper error reporting, not just “parse failed” — signals a class of engineer that most companies will pay for.
Combined with Rung 2 (LRU cache), Rung 3 tells a coherent story: “I know templates AND I know parsing.” That is the profile of someone who can be trusted with a real C++ codebase.
Acceptance Checklist¶
Public GitHub repo named
mini-json-cpp.Zero non-stdlib dependencies at parse time. (Catch2/GTest for tests is fine;
nlohmann/jsonallowed only as a differential-test oracle if you want.)Header-only or header-plus-single-cpp. Documented in the README.
Parses all 10 test JSON files correctly.
Rejects malformed JSON with a
ParseErrorcarrying line and column matching the actual error location, not just “parse failed.”Round-trips:
parse(dump(parse(x))) == parse(x)for every valid test input.Handles Unicode escape sequences (
\uXXXX) including surrogate pairs.Handles all number edge cases in RFC 8259 (leading zero rejection, exponent forms,
-0handling).Compiles clean with
-Wall -Wextra -Wpedantic -Werroron GCC 13 and Clang 17.Runs clean under ASan and UBSan.
CI on GitHub Actions.
README with the 5-section base + a Grammar section (see below) + a Not a Goal section (explicitly disclaim performance vs. simdjson).
Repo shared on r/cpp with
[Show r/cpp]tag.Stretch (recommended): libFuzzer harness in
fuzz/, run for at least 1 hour, no crashes.
README Structure Additions¶
On top of the 5-section base, add:
Grammar — The JSON grammar written out as an EBNF or PEG snippet, right in the README. This is the artifact of understanding — not the code, the grammar.
Error messages — A screenshot or code block showing a real parse error output. This is what people click for. Something like:
ParseError at line 3, col 12: unexpected ',' after array closing bracket ... [1, 2, 3], 4] ^
Not a Goal — Two sentences: this is not simdjson, not nlohmann. It is a teaching implementation. Link to those libraries as “if you need speed.” This disclaimer is important; without it, reviewers will benchmark you and be disappointed.
Common Ways This Rung Fails¶
Error messages just say “parse error.” This kills the whole signal of the rung. The line:col reporting is the artifact. Prioritize it.
The lexer and parser are merged into one giant function. Fine to skip a full lexer, but keep tokenization at least logically separate — it makes error reporting sane.
You handle numbers by
std::stod. Fine for a first pass, but you will hit RFC 8259 edge cases (leading zeros,+sign) thatstodaccepts and JSON rejects. Own the number parsing.You skip Unicode escapes. Then a real JSON with a
\u00e9breaks and your “passes all tests” claim is false. Do surrogate pairs.You benchmark against simdjson and get discouraged. Your parser is 100× slower. It should be. This is not the goal. Re-read the “Not a Goal” section in your own README.
You publish before running the fuzzer. If you add libFuzzer, run it. If it finds a crash the day after you publish, you look worse than if you had never added it.
What Most People Get Wrong¶
They focus on parser cleverness (“look at my elegant std::variant visitor pattern!”) and neglect error messages. But the error messages are the entire signal. A parser that fails cleanly on bad input, with line:col and a snippet, tells a hiring manager: this person has actually operated a parser in anger. A parser that crashes or says “parse failed” tells them: this was a homework assignment.
The second failure mode is scope creep. You are not writing JSON5. You are not writing streaming JSON. You are not writing SAX. You are writing spec-compliant RFC 8259 DOM-style JSON. Anything else eats the month.
Extension Challenges (Rank-Ordered by Signal-per-Hour)¶
libFuzzer harness — highest signal for lowest cost. Run overnight, no crashes = real quality signal.
JSON Pointer (RFC 6901) — add
Value::at("/a/b/0"). Small, cool, useful.Doxygen + gh-pages docs — makes the repo look more mature.
Comparison table in README — lines-of-code, feature parity, and speed vs. nlohmann/json and simdjson. Honesty is a signal.
JSON Schema validation — large scope, probably out of budget for M4. Save for M5 if you have absurd slack.
Links to Source Phase Files¶
Engineering plan:
../02_phase_1_modern_cpp_core/— the P1.2 spec.If parser design feels shaky:
../09_resources/— Crafting Interpreters (Nystrom) chapters 4–6 map directly onto this.Fuzzing tooling setup:
../11_tools_setup/.
Nav: ← Rung 2 · Rung 4 → · Source: Phase 1 · P1.2