{
  "id": 8270800,
  "title": "JSON.parse throws on every token your LLM streams. Here's a 425-byte fix.",
  "url": "https://urgent.news/2026/09/18/json-parse-throws-on-every-token-your-llm-streams-heres-a-425-byte-fix",
  "topic": "ai",
  "section": "AI",
  "published": "2026-09-18T16:26:24.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/acegikmo135/jsonparse-throws-on-every-token-your-llm-streams-heres-a-425-byte-fix-1cne"
  },
  "original_language": "en",
  "account": "In a small app where an LLM returns a recipe as JSON, the UI fills in as the answer streams. The handler encountered issues with JSON.parse throwing on every token or every one of those tokens. This led to two undesirable options: waiting for the whole JSON to finish, which defeats the purpose of streaming, or writing a regex that attempts to close brackets and hoping for the best. The author sought a third option: providing the best value that can be obtained from the data that has arrived so far, without throwing errors or making up values. This led to the creation of a tiny library called SoFar, which is only 425 bytes gzipped, has no dependencies, and performs a single task.\n\nSoFar offers an import statement to access the parsePartialJSON function. It takes a JSON chunk as input and returns the best possible value that can be derived from the partial JSON so far. For example, parsing \"{ title : Pad Th }\" returns an object with the title \"Pad Th\". It can also handle partial objects, arrays, and even malformed JSON like \"{ ok : tru }\", returning an empty object or undefined if the JSON is completely incomplete.\n\nThe library provides a small stateful wrapper called createJSONStream, which allows for streaming JSON parsing. It takes an LLM response stream and feeds it chunk by chunk. For each chunk, it calls the stream.feed() method, which returns the parsed value if successful, or undefined if the chunk is incomplete. Once the entire JSON has been received, JSON.parse() can be called on the raw stream to perform a strict parse.\n\nSoFar works by scanning the buffer once, left to right, while keeping track of open containers (curly braces and square brackets), whether it's inside a string, and a list of safe cut points. Cut points are positions where the JSON is structurally sound, such as after closing a string, opening or closing a container, or before a comma. The library stores snapshots of the stack at these cut points, allowing it to rewind and try again if necessary.\n\nThe library offers two main approaches for parsing partial JSON. The first approach involves closing a string and appending closing brackets for open containers, then passing the result to JSON.parse(). If this fails due to a dangling key or trailing comma, the library walks the cut points backwards, slicing the buffer at each point, appending the stored closers, and attempting to parse again. The first successful parse is returned. If no parse succeeds, the function returns undefined.\n\nSoFar is designed to be a small, lightweight library with no dependencies, making it ideal for use in bundles. It runs at about 1.3 times the cost of a bare JSON.parse() on a 1.6 MB buffer. The library is compared to other existing JSON parsing alternatives, such as partial-json, best-effort-json-parser, jsonrepair, and jsonrepair, which all have larger sizes and may throw errors or make guesses. SoFar stands out for providing never-throwing, never-inventing functionality while being small enough to not require much consideration in a bundle.",
  "summary": "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…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}