If you've used JSON mode (OpenAI, Anthropic, etc.), you've hit this — you want {"answer": "...", "sources": [...]}, but JSON.parse() fails on every incomplete chunk.
LLM responses are inherently append-only (tokens arrive left to right, never go back), so Jsiphon leans into that with three ideas:
1) Append-only parsing — Feed in {"msg": "Hel and get {msg: "Hel"} immediately. Values are only extended, never removed or mutated.
2) Delta tracking — Each snapshot contains only what's new. For a chat bubble, just append delta.content to the DOM — when the LLM produces next chunk "lo, World!", we immediately get {msg: "lo, World!"}. No need to repeat partial JSON parsing or full tree rerendering.
3) Ambiguity tree — A tree that mirrors the shape of your data and tracks which subtrees are finalized at every depth. For example, if you're streaming {"header": {"title": "...", "date": "..."}, "body": "..."}, you can check isAmbiguous(ambiguous.header.title) to use the title the moment it's done, even while header.date and body are still streaming. This isn't a flat "is the whole thing done?" flag — it's per-node stability tracking that propagates up, so isAmbiguous(ambiguous.header) turns false only when all of header's children are finalized.
Existing partial JSON parsers like partial-json and gjp-4-gpt do a great job at the core parsing problem — turning broken JSON into usable objects. Jsiphon builds on that foundation and takes it one step further: instead of just parsing, it gives you a streaming data pipeline where append-only snapshots, per-field deltas, and multi-depth ambiguity tracking all come out of a single async iteration. If you've been using partial-json and wished you knew which fields were done vs still streaming without polling the whole object, that's exactly the gap this fills.
Zero dependencies, never throws on invalid input, handles junk text before/after the JSON root (which LLMs sometimes produce).
GitHub: https://github.com/webtoon-today/jsiphon npm install jsiphon
Would love feedback on the API design — especially the ambiguity tree. Tracking per-node stability across arbitrary nesting depth was the trickiest part. Curious if anyone sees a cleaner approach.
Disclosure: I'm a native Korean speaker. I used Claude to help structure and translate this post into English. The ideas and code are mine.