Toolkit Labs

the full leaderboard

JSON inside a code fence or prose — 21 parsers

A model that was asked for JSON very often returns JSON plus something else: three backticks and the word json around it, or a sentence before it and an offer to help after it. The document is not malformed — it is correct JSON with text attached — and that is why a stricter or a looser parser both fail on it. This page is two categories of the leaderboard read on their own: 21 parsers, 11 Python and 10 JavaScript, on 50 such cases.

Corpus: MALFORMED-300, sha256 ded36c275f9ff481a9e264f9e900ccbb16da9cf430ff6fe8ffc23b9c02acb88a. Python measured 2026-08-19T12:31:18Z on Python 3.12.3; JavaScript 2026-08-19T12:33:03Z on node v25.8.2. One run per library, nothing tuned afterwards. Both raw run files are published: leaderboard.json · leaderboard_js.json.

fenced: the object sits inside a ```json block, sometimes with a language tag, sometimes with more than one block. prose_wrapped: the object is surrounded by ordinary sentences with no fence at all. 25 labelled cases each.

Python

parserfence recoveredprose recoveredrefusedreturned a wrong valuesame library, all 300
json-repair 0.63.225 / 2525 / 2500265 / 300
jsonshim (Toolkit Labs)25 / 2525 / 2500282 / 300
commentjson 0.9.00 / 250 / 2550058 / 300
demjson3 3.0.50 / 250 / 25500110 / 300
dirtyjson 1.0.80 / 250 / 25500123 / 300
hjson 3.1.00 / 250 / 2550085 / 300
json.loads (stdlib control)0 / 250 / 2550025 / 300
json5 0.15.00 / 250 / 25500118 / 300
partial-json-parser 0.2.1.1.post70 / 250 / 2550047 / 300
pyjson5 2.0.10 / 250 / 25500118 / 300
simplejson 4.1.10 / 250 / 2550029 / 300

JavaScript / TypeScript

parserfence recoveredprose recoveredrefusedreturned a wrong valuesame library, all 300
jsonc-parser 3.3.118 / 258 / 25240157 / 300
best-effort-json-parser 1.5.117 / 250 / 25726160 / 300
jsonrepair 3.15.014 / 250 / 25432205 / 300
JSON.parse (stdlib control) v25.8.20 / 250 / 2550025 / 300
hjson 3.2.20 / 250 / 2550095 / 300
json-loose 1.2.40 / 250 / 2550080 / 300
json5 2.2.30 / 250 / 25500118 / 300
partial-json 0.1.70 / 250 / 2550074 / 300
untruncate-json 0.0.10 / 250 / 2550047 / 300
dirty-json 0.9.20 / 250 / 25050110 / 300

Three outcomes per case, and they add to 25 in each category for every row: the parser returned the expected value, or it refused, or it returned something else.

The short answer

json-repair and jsonshim (Toolkit Labs) recover all 25 fenced and all 25 prose-wrapped cases. Every other Python parser in this run recovers none of the 50 — 9 of 11 at zero — and all of them refuse rather than guess: 450 refusals, 0 wrong values in the whole Python run of this page. In JavaScript the best fence result is jsonc-parser at 18 of 25 and the best prose result is jsonc-parser at 8 of 25; 7 of the 10 JavaScript parsers recover none of the 50.

The reason is the same for the dialect parsers in both languages. json5, pyjson5, hjson, commentjson and their neighbours read a looser grammar: comments, trailing commas, unquoted keys. Backticks and English are not a looser grammar, they are a different document. Extraction has to happen before parsing, and a parser that only parses will refuse — correctly.

Where it gets expensive

Across both languages a parser returned a value that was not the expected one, without refusing, 108 times on these 50 cases — 0 in Python and 108 in JavaScript. The widest is dirty-json at 50 of 50. Prose is where this concentrates: a parser that scans for the first plausible brace can pick up a number out of a sentence, or the first of two blocks when the second was the answer. A refusal is a branch in your code. A wrong value is a row in your database that looks exactly like data.

If you only want the extraction

You do not need a parser for this at all. Pull the fenced block out, or take the outermost balanced brace span, and hand the result to the strict parser you already have — the stdlib control refuses all 50 of these cases, and after extraction it is the correct tool again. The two Python libraries above do exactly that step internally before they parse.

Two of the measured libraries run in your browser on the in-browser tool, on your own text, uploading nothing. The category that cannot be fixed this way is truncation, which is its own page.

Check it yourself

The per-parser counts behind both tables are published as one small file, so nothing here rests on this page’s word: fenced.json.

curl -O https://toolkitlabs.org/malformed300/open12.jsonl
curl -O https://toolkitlabs.org/malformed300/score.py
python3 score.py --corpus open12.jsonl --parser yourmodule:recover

The free sample carries 2–3 cases from each of the 12 categories, fenced and prose-wrapped among them, and is CC0 forever.

What this page does not tell you

The full corpus

The 12 open cases and the scorer are CC0 forever, bought or not. The paid product is the remaining 270 labelled cases with the rationale for each label — fenced and prose-wrapped among them — what lets you run this measurement on your own parser at full resolution.

Single developer — €29 · Team / CI licence — €99

Instant download after payment; digital goods, delivered immediately. Payments processed by Stripe. Prices include VAT where applicable.