# jsonshim

**Get the JSON out of a model's answer, or find out you can't.**

You asked for JSON. You got a code fence, an apology, a trailing comma, a single
quote, and a reply that stopped mid-object because the token budget ran out.
`json.loads` tells you `Expecting value: line 1 column 1 (char 0)` and nothing else.

jsonshim finds the JSON-shaped span, repairs the shapes that are genuinely
repairable, and reports exactly what it changed. When it cannot recover a value it
returns an error and no value. It never fills in a field to avoid an exception —
a confidently wrong object is worse than a stack trace, because the wrong object
gets written to a database.

One file, 18 KB. Standard library only. No install, no network, no telemetry,
no dependency added to your tree. Public domain (CC0).

```
python3 jsonshim.py --selftest        # 37/37, every case listed in the file
cat reply.txt | python3 jsonshim.py   # JSON on stdout, report on stderr, exit 1 if unrecoverable
```

```python
from jsonshim import extract

r = extract(model_reply)
if r.ok:
    use(r.value)          # r.repairs: what was changed. r.span: where it was found.
else:
    retry(r.error)        # no value invented, ever
```

## What it does

````
$ printf '%s' 'Sure! Here is the call:
```json
{"tool": "search", "args": {"q": "a, b",}, "note": "cut off here' | python3 jsonshim.py --pretty

jsonshim: recovered from span 32..96; repairs: trailing comma removed, truncated input: closed 1 container, unterminated string closed
{
  "tool": "search",
  "args": {
    "q": "a, b"
  },
  "note": "cut off here"
}
````

Prose before and after · fenced blocks, including a fence that never closes ·
trailing commas · single-quoted strings · unquoted keys · `True` / `False` /
`None` / `NaN` / `undefined` · `//` and `/* */` comments · raw newlines inside
strings · mismatched brackets · empty array elements · and truncation, which is
the one that actually matters in production: a reply cut off mid-string,
mid-number, mid-key or four containers deep is closed back up and the incomplete
tail is dropped rather than guessed.

Braces inside strings do not fool the span finder. `{"a": "}"}` parses.

## The numbers

The 33 cases inside `jsonshim.py` were used while writing the repairs, so anything
measured on them is in-sample and worth nothing as a claim. They are published
anyway, and `--selftest` runs them, because a test you can read is better than a
percentage you can't.

The benchmark below is different. Those 30 cases were written after the tool was
finished, run exactly once, and never tuned on. Recovery means the returned value
*equals* the value a human would have written down — not that something parsed.

```
$ python3 bench.py
held-out cases: 30   (written after the tool, run once, never tuned on)
  json.loads baseline exact-match :  9/30  (30.0%)
  jsonshim exact-match            : 28/30  (93.3%)

refuse cases: 3   (any value returned here is a failure)
  jsonshim invented a value       : 0

held-out failures, listed because hiding them would make the number a lie:
  array of tool calls, truncated         -> [{'tool': 'a'}, {'tool': 'b'}, {}]
  colon inside an unquoted-looking url value -> {}
```

Those two failures stay unfixed. Repairing them now would mean tuning on the
held-out set, and 93.3% would stop being a first-run number and start being a
sales figure. They are the honest edges: a truncated object that had a key but no
value becomes `{}` instead of being dropped, and a bare `http://a.b` used as a
value loses to the colon. Neither invents data; both are visible in `r.value`.

**What this benchmark cannot tell you** is how jsonshim does on *your* traffic.
The corpus was written by the same author as the tool. It shows that 30 named
shapes are handled and that the standard library handles 9 of them. Run
`bench.py` with your own failures pasted in — that is the number worth having.

## Design rules

1. Never invent a value. A missing field stays missing.
2. Report every repair. Silent correction is how bad data gets trusted.
3. Prefer the truncation repair, because that is the failure mode that scales
   with your output length and your bill.
4. No dependencies. A JSON repair library that pulls in a parser generator has
   made your problem worse.
5. `want="object"` / `want="array"` when you know the shape and the reply
   contains both.

## Licence

CC0 1.0 Universal. Public domain. No attribution required, no warranty given.
Copy the file into your repo and delete this README.

Tips are optional and buy nothing — there is no paid tier, no key, no account,
and nothing is withheld.
