JSON.parse throws on every token your LLM streams. Here's a 425-byte fix. A developer built SoFar, a 425-byte gzipped, zero-dependency JavaScript library that parses partial JSON as an LLM streams tokens, returning the best complete value available instead of throwing. The library scans the buffer once in O(n) time, tracking open containers and cut points, and falls back to rewinding to earlier cut points when a simple close-and-parse attempt fails. Benchmarks show it runs at roughly 1.3× the cost of a bare JSON.parse on a 1.6 MB buffer, and unlike alternatives such as partial-json and jsonrepair it never throws and never invents values. I was building a small app where an LLM returns a recipe as JSON and the UI fills in as the answer streams. Simple idea. It fell over immediately. Here's what my handler actually saw between tokens: {"title": "Pad Th {"title": "Pad Thai", "ingr {"title": "Pad Thai", "ingredients": "rice noo JSON.parse throws on every one of those. Every single token, until the very last } . So you have two bad options. Wait for the whole thing to finish — which throws away the reason you're streaming in the first place. Or write a regex that closes some brackets and hope. Mine broke on the first escaped quote. I wanted a third option: on every chunk, give me the best value you can honestly get from what's arrived so far. Never throw. Never make things up. That turned into a tiny library called SoFar . It's 425 bytes gzipped, has zero dependencies, and does exactly one thing. js import { parsePartialJSON } from "sofar-json"; parsePartialJSON '{"title": "Pad Th' ; // → { title: "Pad Th" } parsePartialJSON '{"title": "Pad Thai", "ingr' ; // → { title: "Pad Thai" } parsePartialJSON '{"title": "Pad Thai", "servings": 4,' ; // → { title: "Pad Thai", servings: 4 } parsePartialJSON '{"ok": tru' ; // → {} parsePartialJSON '' ; // → undefined Notice the fourth one. tru is probably going to be true , but the buffer doesn't say that yet, so you get the last value that was actually complete. A partial string comes back as a string, because a prefix of a string is still a string. A partial number or literal doesn't get guessed. For the streaming case there's a small stateful wrapper: js import { createJSONStream } from "sofar-json"; const stream = createJSONStream ; for await const chunk of llmResponse { const value = stream.feed chunk ; if value == undefined render value ; } JSON.parse stream.raw ; // strict parse once it's done That's the whole API. Two functions. I didn't want a second JSON parser. JSON.parse is fast, correct, and already in every runtime. I just needed to hand it something it could accept. So the library scans the buffer once, left to right, keeping track of three things: { and Each cut point stores a snapshot of the stack at that moment. Not a copy — the stack is kept as the string of closing brackets you'd need to append, innermost first, so a snapshot is just a reference. Rewinding later is a slice and a concat. No rescanning. Then it tries two things: Attempt 1. If we're inside a string, close it. Append the closers for every open container. JSON.parse . This handles almost everything: a string cut mid-word, an array mid-element, an object mid-value. Attempt 2. If that fails — a dangling key like {"a": 1, "b , a trailing comma, a half-typed fals — walk the cut points backwards. Slice the buffer at each one, append that cut's stored closers, try again. The first one that parses wins. If nothing parses, return undefined . Never throw. There's a bit of extra care for escapes. A trailing \ gets dropped, a half-finished \u00e gets trimmed back to the backslash, but \\ at the end of a string is left alone because that's a complete escape. The scanner already knows which case it's in, so this costs nothing. The whole thing is one pass, O n , and on a 1.6 MB buffer it runs at about 1.3× the cost of a bare JSON.parse . I checked the alternatives with the same inputs before writing this. Sizes are esbuild + gzip. | Input | SoFar | partial-json | best-effort-json-parser | jsonrepair | |---|---|---|---|---| | Size | 425 B | 1.6 kB | 1.9 kB | 3.7 kB | | '' | undefined | throws | "" | throws | | }}} | undefined | throws | returns "}}}" | throws | | {"ok": tru | {} | {ok: true} | {ok: true} | {ok: "tru"} | | {"a":"x","ingr | {a: "x"} | {a: "x"} | {a: "x"} | {a: "x", ingr: null} | They're all fine libraries and I'd used a couple of them. What I wanted was different: never throw, never invent, and be small enough that I'd never think about it in a bundle. If your JSON has broken syntax — single quotes, comments, unquoted keys — jsonrepair is the right tool, and the two chain nicely. 4. is 4 . That could be 4.5 . There's a playground where you can paste any JSON and scrub through it a character at a time. It shows the exact string handed to JSON.parse , which characters were synthesized, which were rewound past, and every cut point the scanner recorded: https://acegikmo135.github.io/sofar/ https://acegikmo135.github.io/sofar/ npm install sofar-json Source, tests, and the fuzz test that feeds a document one character at a time are on GitHub: https://github.com/acegikmo135/sofar https://github.com/acegikmo135/sofar If you've got a stream it handles badly, the most useful thing you can send me is the raw buffer as a string. It becomes a test case.