Streaming JSON From LLMs Kept Breaking My UI
npm i llm-json-validator. Not a typo. Here's the bug behind partial JSON from LLMs, why it wrecks your UI, and the little buffer I built to fix it.

Atharv Lingayat
Software Engineer

If you've ever piped streaming JSON from an LLM straight into a component that wants clean, structured data, you've probably watched it fall apart. It kept happening to me while building agentic and copilot UIs, so I finally sat down and built a small tool for it:
npm i llm-json-validatorNo, I didn't paste that by mistake. Here's the bug, why it happens, and how a little buffer between the stream and your UI makes it go away.
Why streaming JSON breaks your UI
Modern AI features stream. The model writes its answer token by token, and you render as it arrives so the UI feels responsive. The problem is that for almost the whole stream, the JSON isn't valid JSON yet. There's a half-open string here, an unclosed bracket there, a comma sitting around waiting for a value that hasn't been written.
Your component has no idea the data is still coming. It gets a fragment, assumes it's the finished thing, and tries to render it. You can guess how that goes when you hand a parser something that can't be parsed yet:
const chunk = '{"title":"Weekly report","items":[{"name":"Sig';
JSON.parse(chunk);
// ✗ SyntaxError: Unexpected end of JSON input
// your component throws, unmounts, or renders garbageSo you're left with two bad options. Buffer the whole response before you render anything, which kind of defeats the point of streaming. Or write string patching that closes brackets with regexes and hope, which breaks the next time the model changes its output. I've shipped both. Neither one holds up.
The fix: a buffer between the stream and your UI
llm-json-validator sits between the raw stream and your components. It takes whatever has arrived and shapes it into something valid and predictable. It balances the quotes, closes the open brackets, and fills the fields that haven't shown up yet with safe placeholders. Every frame your UI gets back is renderable, so you can draw partial state without wrapping everything in try/catch.
import { validateStreamingJson } from "llm-json-validator";
const chunk = '{"title":"Weekly report","items":[{"name":"Sig';
validateStreamingJson(chunk, { returnParsedJson: true });
// ✓ { title: "Weekly report", items: [{ name: "Sig" }] }
// always valid, render it directlyThe idea is deliberately simple: your component should never see broken JSON. It gets a valid object every time, and that object only grows toward the final answer. No hacks, no fragile workarounds, and no bugs that only show up in prod.
How it works
Under the hood it's an append-aware parser. Instead of choking on a fragment, it tracks where it is and finishes the shape on demand:
- It balances quotes and closes open brackets so the fragment can actually be parsed.
- It's stateful across chunks, so you feed it what the network hands you and get valid JSON back at every step.
- You pick the placeholder values for fields that haven't arrived yet, so your types stay happy.
- Zero dependencies, written in TypeScript, sitting at 95%+ test coverage. Small enough to drop into anything.
Wiring it into a streaming UI
For a real stream, createStreamingParser holds the context between chunks, so you just hand it what you get and render the result:
import { createStreamingParser } from "llm-json-validator";
const parser = createStreamingParser({ returnParsedJson: true });
for await (const chunk of stream) {
const data = parser.appendChunk(chunk);
// data is a valid object every iteration
render(data);
}Treat every streamed frame as valid, and never fall back to empty. Hold onto the last good structure and only move forward. That one rule kills a whole category of streaming-UI bugs.
One gotcha worth knowing
There's one honest edge case. If the buffer ends in the middle of a key, where the model has typed the property name but not the colon or value yet, there's nothing to complete it into, so that field can't come back to you. Render each frame blindly and you'll see it flicker: a field pops in, vanishes for a beat, then returns. The fix is the same rule from above. Latch the last valid object on your side so the UI only moves forward. It's a one line guard, and honestly worth adding to any streaming renderer whether you use this library or not.
Where it's going
People are already using it. The package just passed its first hundred downloads, and that's only the start. I built it with React and streaming UIs in mind, but it doesn't care what framework you use. Anywhere you're reading partial JSON from a model, it fits. If you're building AI interfaces, I'd love to know how you're handling streaming JSON right now.
llm-json-validator is open source and MIT licensed. Grab it with npm i llm-json-validator, or read the source and docs on npm and GitHub.
Thanks for reading