jsonmend
Mends the JSON your LLM almost wrote.
Truncated tool calls, markdown fences, single quotes, bare keys, Python literals, comments, trailing commas, prose around the payload — jsonmend turns them into valid JSON. It is a drop-in replacement for json_repair that is 5–10× faster on batch repair, ~50× faster on streaming, ships a true incremental streaming API (O(new bytes) per chunk, not O(buffer)), and is the reference implementation of an open, cross-language conformance corpus for JSON repair.
Pure Python, zero dependencies, zero binaries. Works on CPython 3.9–3.14,
PyPy, Pyodide/WASM, AWS Lambda — anywhere pip install works.
pip install jsonmend
Scoreboard
JSON repair has no standard: the same broken input is repaired differently by the Python and JavaScript incumbents, which is a real source of production bugs. The jsonmend conformance corpus (485 cases, 20 categories, CC0) defines repair semantics as data — including the genuinely ambiguous cases, where every defensible answer is accepted.
| jsonmend 0.1.1 | json_repair 0.60.1 | jsonrepair 3.14.0 (JS) | |
|---|---|---|---|
| corpus pass rate | 485/485 (100%) | 339/485 (69.9%) | 365/485 (75.3%) |
Per-category breakdown: corpus/scoreboard.md.
Reproduce: python tools/referee.py --write (needs pip install json_repair and npm install jsonrepair, dev-only).
Performance
Median of 7, three independent rounds within ±5%, Python 3.12, M-series
macOS. All inputs are broken JSON (the json.loads fast path never
runs). Verified-then-timed: outputs are checked equal before timing.
Reproduce: python tools/bench.py --verify && python tools/bench.py.
| workload | size | jsonmend | json_repair | speedup |
|---|---|---|---|---|
| truncated tool call | 1 KB | 0.027 ms | 0.199 ms | 7.3× |
| truncated row payload | 75 KB | 1.48 ms | 12.6 ms | 8.5× |
| markdown-fenced output | 49 KB | 0.25 ms | 2.6 ms | 10.6× |
| dirty (quotes/keys/literals) | 5 KB | 0.38 ms | 2.6 ms | 7.0× |
Streaming is a different complexity class
A streaming UI re-renders the partial value on every chunk. With a batch
repairer you must re-parse the whole buffer each time — O(n²) total. The
stateful Mender only pays for the new bytes (amortised O(1) per fed
character on CPython — guarded by a buffer-copy invariant test, not a
flaky timer):
| workload | jsonmend Mender |
json_repair (stream_stable=True) |
|
|---|---|---|---|
| 150 KB in 4 KB chunks | 6.9 ms | 323 ms | 47× |
| 10 MB in 4 KB chunks | 1.2 s | est. >20 min (quadratic) | — |
Usage
Drop-in for json_repair
# before
from json_repair import repair_json, loads
# after — same call sites
from jsonmend import repair_json, loads
repair_json("{'name': 'John', age: 31") # '{"name": "John", "age": 31}'
loads('```json\n{"ok": true,}\n```') # {'ok': True}
repair_json(json_str, return_objects=..., skip_json_loads=..., ensure_ascii=..., **json_dumps_args), loads, load(fd),
from_file(path) match json_repair's signatures. Valid JSON
short-circuits through C-speed json.loads.
Streaming
from jsonmend import Mender
m = Mender()
for chunk in llm_stream: # feed as the tokens arrive
partial = m.feed(chunk) # best-effort value, O(new bytes)
render(partial) # e.g. {"answer": "The capital of Fr"}
value = m.close() # final mended value
feed() returns a live view that grows in place — including the string
that is currently streaming in. Any chunking gives byte-identical results
to batch repair (property-tested over the whole corpus).
Strict mode
from jsonmend import loads, JSONMendError
loads("complete garbage") # "" (json_repair-compatible)
loads("complete garbage", strict=True) # raises JSONMendError
What it fixes
truncated objects/arrays/strings/numbers/literals · markdown fences with
prose around them · single/smart/backtick quotes · unescaped inner quotes
· missing quotes · bare keys and values · True/False/None/undefined/NaN/ Infinity · //, #, /* */ comments · trailing/missing/extra commas ·
missing colons · mismatched brackets · concatenated/NDJSON documents ·
string concatenation ("a" + "b") · JSONP/MongoDB wrappers
(ObjectId("…")) · Python tuples/sets · ellipsis placeholders ·
non-string keys · BOM and exotic whitespace · escaped-JSON documents
({\"a\": 1}) · broken \u escapes and surrogate pairs · 100k-deep
nesting (no recursion anywhere)
Why it's fast
- One resumable state machine serves batch and streaming — batch is a single feed that never suspends, so there is no streaming tax.
- Strings cost one
str.find+ one slice when clean; never a per-character Python loop. - Speculative C parsing: complete sub-trees inside broken documents
are recognized and handed to the C
jsonscanner, with a salvage step that parses the longest clean prefix of a broken container in one shot. Semantics-affecting inputs (NaN, control chars, surrogate escapes) fall back to the machine, so behavior never changes. - Bounded backtracking: a string-close decision can revisit one recorded candidate quote, never rescan; adversarial quote storms stay linear (tested).
Guarantees
- Output is always valid RFC 8259 JSON (or
""/an exception). Unlike json_repair,NaN/Infinitynever leak into the output text — they serialize asnull(loadsstill gives you the floats). - Output is always UTF-8 encodable (lone surrogates are replaced).
- Never crashes, never recurses: fuzzed and property-tested, 100k-deep inputs are fine.
Mender.close()≡ batch result, for every chunking (property-tested).
Honest differences vs json_repair
logging=Trueis not supported (it is incompatible with the single-pass design and is one reason json_repair is slow); a no-op shim raisesTypeErrorso you notice.- Schema-guided repair (
schema=) is not implemented in v0.1. - json_repair's
stream_stable=Trueflag changes how truncated escapes render mid-stream; jsonmend'sMenderis always stream-stable. - On
ambiguouscorpus cases the libraries may legitimately differ; jsonmend's choices are documented case-by-case in the corpus rationales.
The corpus is the point
If you maintain a JSON-repair library in any language: please steal corpus/. It is CC0, the format is three fields, and 485 cases with rationales are more valuable than any of our engines. Cross-language agreement on repair semantics helps everyone shipping LLM systems.
License
MIT. The conformance corpus is CC0.
Release files for jsonmend 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jsonmend-0.1.4.tar.gz | 30.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jsonmend-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:53.4 kB
Release files / jsonmend-0.1.4.tar.gz
| Download URL | jsonmend-0.1.4.tar.gz |
|---|---|
| Size | 30.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d9553cc2010dc21201c087e31d7737026587dbf6f6c93a245e5fc4b33f899998
|
|
BLAKE2b-256 checksum How to use checksums |
080a63bad484ca13e87c1ef97be00de432a31926474dd5202722d735501c80e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 17, 2026.
Transparency logRelease files / jsonmend-0.1.4-py3-none-any.whl
| Download URL | jsonmend-0.1.4-py3-none-any.whl |
|---|---|
| Size | 22.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a6316fb852b30d3355c38cc09084a5bddab849b4ca903d8f4003c931582ebc2b
|
|
BLAKE2b-256 checksum How to use checksums |
d69801174659d3ef29b9db468ef55e75f447252235d84066e959f9ded734f9e6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 17, 2026.
Transparency log