You Asked for JSON. You Got a Code Fence and a Trailing Comma
The model gave you JSON wrapped in a code fence, with a trailing comma, using single quotes, preceded by "Sure, here you go!" and cut off mid-string because it hit the token limit. Your parser throws. Your pipeline dies. You retry the whole expensive call and hope this time is different.
There are two honest fixes. Constrain the decoding so only valid tokens ever come out (the grammar approach behind tools like Outlines), or repair the almost-valid output after the fact. structstream does the second, plus the part everyone forgets: it checks the repaired value actually matches your schema, because valid-but-wrong JSON breaks your code exactly the same way invalid JSON does.
Every Fix Targets a Specific, Common Failure
• stripped_code_fence: handles the ```json wrapper and bare ``` fences models love to add.
• extracted_json_span: strips the "Here is your JSON:" prose models put before and after the object.
• quoted_bare_keys and single_to_double_quotes: fix the Python-flavored JSON models produce when they are half-remembering a dict literal.
• closed_open_brackets and closed_open_string: recover objects the model cut off because it ran out of tokens mid-response.
Repair Without a Schema Check Is Half a Fix
A model can hand you flawless JSON with the wrong keys or a string where you needed a number, and your code downstream breaks exactly the same way it would from invalid JSON. The bundled validator supports the common subset (typed properties, required keys, typed arrays) so the benchmark only counts a sample as recovered if it clears both bars: parses, and matches.
The Number
Raw json.loads parses 1 out of 15 realistic malformed samples, 7%. structstream's repair recovers all 15, 100%, and every one of those 15 failure types is something a real model actually produces, not a synthetic edge case.
15 tests pass in CI across Python 3.9, 3.11, and 3.13. Pure stdlib, no eval anywhere near it.