@cacheplane/json-stream
v0.1.1
Published
Streaming incremental JSON parser with push/pull APIs, node guards, and JSON Pointer resolution.
Maintainers
Readme
@cacheplane/json-stream
Incremental JSON parser for input that arrives a chunk at a time.
Use it when an AI model, network stream, worker, or tool call emits JSON
progressively. JSON.parse() needs a complete document; this parser gives you a
typed tree while the document is still arriving, marking each node partial or
complete so you can render what has landed and leave the rest.
Install
npm install @cacheplane/json-streamRuntime and packaging:
- Node
>=20 - TypeScript declarations included
- ESM and CJS bundled
- Zero runtime dependencies
- Marked side-effect free
30-Second Example
import { create, push, finish, isObjectNode } from "@cacheplane/json-stream";
let state = create();
for (const chunk of ['{"name": "Ada', '", "age": 3', "6}"]) {
state = push(state, chunk);
}
state = finish(state);
if (isObjectNode(state.root)) {
// Every node carries a status, so a half-arrived string is still readable.
console.log(state.root.status); // "complete"
}Mental Model
Three functions drive the whole parser, and each returns a new StreamState
rather than mutating the one you passed:
create()— a fresh, empty state.push(state, chunk)— feed the next chunk; safe to call at any byte boundary, including mid-token and mid-escape-sequence.finish(state)— signal end of input. Anything still open is resolved or reported as aStreamError.
Every node in the tree carries a status of partial or complete. That is
the distinction the whole library exists for: a consumer can render a string
that is still arriving, and know not to treat it as final.
Structured Errors
Resolved object values preserve JSON keys as own enumerable, writable,
configurable data properties on ordinary objects, including the empty key and
names such as __proto__, constructor, and toString. Duplicate keys keep the
last value, matching JSON.parse. Defining a JSON key does not change the
result object's prototype.
When parsing fails, state.error contains a StreamError with source location,
a human-readable message, and a stable code from the public
StreamErrorCode union:
type StreamErrorCode =
| "INVALID_SYNTAX"
| "UNEXPECTED_END"
| "TRAILING_CONTENT";INVALID_SYNTAXmeans a token or JSON grammar rule is malformed, including a completed primitive that cannot parse.UNEXPECTED_ENDmeansfinish()found input that ended while a value or container was still an incrementally valid prefix. This includes number prefixes such as-,1., and1e+, which could become valid with more input.TRAILING_CONTENTmeans more non-whitespace input arrived after the root JSON value was complete.
Use code for control flow. Messages remain useful for people and diagnostics,
but their exact wording is not part of the control-flow contract. Error states
are terminal: later push() and finish() calls preserve the same structured
error state.
Type Guards
Narrowing helpers for each node kind, plus a status check:
import {
isArrayNode,
isBoolNode,
isComplete,
isNullNode,
isNumberNode,
isObjectNode,
isStringNode,
} from "@cacheplane/json-stream";isComplete(node) is the one to reach for when deciding whether a value is
safe to commit downstream.
JSON Pointer Lookup
resolve reads a node out of the tree by RFC 6901 pointer, which is
convenient when you care about one field of a large streaming document:
import { resolve } from "@cacheplane/json-stream";
const node = resolve(state.root, "/choices/0/message/content");It returns undefined when the path does not exist yet — a normal condition
mid-stream, not an error.
Related
@cacheplane/partial-json— a richer partial-JSON parser with identity preservation and structural-sharing materialization.@cacheplane/partial-markdown— the same idea for streaming Markdown.
License
MIT
