@cacheplane/partial-json

中文文档
0.3.1发布于 2026/10/01MITTypeScriptESM + CJS构建来源已验证近 30 天下载 7307

Streaming partial-JSON parser with identity preservation, push/pull APIs, JSON Pointer lookups, and structural-sharing materialization.

原始使用文档
文档目录21 节
  1. Install
  2. 30-Second Example
  3. When To Use It
  4. Mental Model
  5. Push-Style API
  6. JSON Pointer Lookup
  7. Pull-Style API
  8. Structural-Sharing Snapshots
  9. Guarantees
  10. Limits
  11. Node Shape
  12. Type Guards
  13. Common Patterns
  14. Render A Streaming Response
  15. Watch A Specific Field
  16. Read Partial String Content
  17. Detect End Of Document
  18. Troubleshooting
  19. API Reference
  20. Changelog
  21. License

@cacheplane/partial-json

Streaming JSON parser for incomplete input.

Use it when an AI model, network stream, worker, or tool call emits JSON a chunk at a time. JSON.parse() needs a complete document. This parser gives you a typed tree while the document is still arriving, and keeps node object identity stable as later chunks update the tree.

Install

npm install @cacheplane/partial-json

Runtime and packaging:

  • Node >=20
  • TypeScript declarations included
  • ESM and CJS bundled
  • Uses @cacheplane/json-stream as its runtime parser kernel
  • Marked side-effect free

30-Second Example

import { createPartialJsonParser } from '@cacheplane/partial-json';

const parser = createPartialJsonParser();

parser.push('{"items":[{"name":"alpha","done":');
parser.push('true},{"name":"beta');

const first = parser.getByPath('/items/0/name');
const second = parser.getByPath('/items/1/name');

console.log(first);
// { type: 'string', value: 'alpha', status: 'complete', ... }

console.log(second);
// { type: 'string', value: 'beta', status: 'streaming', ... }

The parser does not throw just because the input ends mid-token. Every public node carries status: 'pending' | 'streaming' | 'complete', so a UI can render what exists now and let nodes resolve in place.

When To Use It

Use @cacheplane/partial-json when you need to:

  • Render structured LLM output before the model finishes.
  • Watch one field in a streaming tool-call argument object.
  • Preserve React, Angular, Solid, or custom renderer memoization across chunks.
  • Convert a live parser tree into plain JS snapshots without replacing unchanged subtrees.
  • Parse strict JSON incrementally without regex trimming or repair loops.

It is not a JSON repair library. Invalid JSON still becomes an error; truncated JSON is treated as ordinary in-progress input.

Mental Model

The parser builds a node tree. Nodes are mutated in place as more input arrives. Each node has:

  • id: stable numeric identity for the lifetime of the parser.
  • type: JSON node type.
  • status: pending, streaming, or complete.
  • parent: parent node or null.
  • key: object property key, array index, or null for the root.

Container nodes expose children:

  • Object nodes use children: Map<string, JsonNode>.
  • Array nodes use children: JsonNode[].

Scalar nodes expose values:

  • String nodes expose partial value while streaming.
  • Number nodes expose raw while streaming and parsed value after completion.
  • Boolean and null nodes remain pending until the literal resolves.

Push-Style API

Use the push-style API for streaming UIs and long-lived node references.

import {
  createPartialJsonParser,
  materialize,
} from '@cacheplane/partial-json';

const parser = createPartialJsonParser();

for await (const chunk of llmStream) {
  const events = parser.push(chunk);

  for (const event of events) {
    if (event.type === 'value-updated') {
      // event.node is the same object reference across future pushes.
    }
  }

  if (parser.root) {
    const snapshot = materialize(parser.root);
    render(snapshot);
  }
}

parser.finish();

push(chunk) and finish() return ParseEvent[]:

interface ParseEvent {
  type: 'node-created' | 'value-updated' | 'node-completed';
  node: JsonNode;
  delta?: string;
}

JSON Pointer Lookup

getByPath() accepts JSON Pointer paths:

parser.getByPath('');                  // root
parser.getByPath('/items/0/name');     // array index + object key
parser.getByPath('/a~1b/~0key');       // RFC 6901 escaping

Missing paths return null.

Pull-Style API

Use the pull-style API when you want immutable parser state, such as reducers, undo/redo stacks, deterministic tests, or state-machine integrations.

import { create, push, finish, resolve } from '@cacheplane/partial-json';

let state = create();
state = push(state, '{"a":1');
state = push(state, ',"b":2}');
state = finish(state);

const value = resolve(state);
// { a: 1, b: 2 }

Each call returns a new StreamState.

The pull-style exports also include StreamError and StreamErrorCode from @cacheplane/json-stream, including the stable INVALID_SYNTAX, UNEXPECTED_END, and TRAILING_CONTENT categories. Branch on code, not exact error-message text. UNEXPECTED_END includes incrementally valid number prefixes such as -, 1., and 1e+; completed malformed grammar is INVALID_SYNTAX.

Structural-Sharing Snapshots

Materialized object values preserve JSON keys as own enumerable, writable, configurable data properties on ordinary objects, including 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. The json-stream ^0.1.1 dependency also preserves empty keys while parsing.

materialize(node) converts a parser node tree into a plain JavaScript value. It uses a WeakMap cache keyed by node identity, so unchanged subtrees return the same object reference across calls.

const before = materialize(parser.root!);

parser.push(',"next":true');

const after = materialize(parser.root!);

This is useful when rendering every animation frame or every stream chunk. Consumers that compare references only re-render subtrees that actually changed.

Guarantees

@cacheplane/partial-json guarantees:

  • Truncated valid-prefix JSON is parseable as in-progress input.
  • Public push-style node object identity is stable across pushes.
  • Node status uses the same pending | streaming | complete lifecycle as @cacheplane/partial-markdown.
  • materialize() preserves references for unchanged subtrees.
  • Duplicate object keys follow normal JavaScript materialization semantics: later values win.
  • finish() closes valid trailing constructs that can complete at end of input, such as a number waiting for a terminator.
  • Syntax errors are recorded in parser state rather than hidden by best-effort repair.

Limits

This package does not:

  • Repair invalid JSON.
  • Accept JSON5 features such as comments, single-quoted strings, trailing commas, unquoted object keys, .5, or +1.
  • Preserve duplicate object-key history in materialized output.
  • Validate application-level schemas.
  • Stream multiple top-level JSON documents through one parser instance.

Create a new parser for each top-level document.

Node Shape

interface JsonNodeBase {
  readonly id: number;
  readonly type: 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null';
  status: 'pending' | 'streaming' | 'complete';
  parent: JsonNode | null;
  key: string | number | null;
}

Object nodes:

interface JsonObjectNode extends JsonNodeBase {
  readonly type: 'object';
  children: Map<string, JsonNode>;
  pendingKey: string | null;
}

Array nodes:

interface JsonArrayNode extends JsonNodeBase {
  readonly type: 'array';
  children: JsonNode[];
}

Scalar nodes:

interface JsonStringNode extends JsonNodeBase {
  readonly type: 'string';
  value: string;
}

interface JsonNumberNode extends JsonNodeBase {
  readonly type: 'number';
  raw: string;
  value: number | null;
}

interface JsonBooleanNode extends JsonNodeBase {
  readonly type: 'boolean';
  value: boolean;
}

interface JsonNullNode extends JsonNodeBase {
  readonly type: 'null';
}

Type Guards

import {
  isObjectNode,
  isArrayNode,
  isStringNode,
  isNumberNode,
  isBoolNode,
  isNullNode,
  isComplete,
} from '@cacheplane/partial-json';

const node = parser.getByPath('/title');

if (node && isStringNode(node) && isComplete(node)) {
  console.log(node.value);
}

Common Patterns

Render A Streaming Response

const parser = createPartialJsonParser();

for await (const chunk of llmStream) {
  parser.push(chunk);
  render(parser.root);
}

parser.finish();

Watch A Specific Field

parser.push(chunk);

const status = parser.getByPath('/result/status');

if (status?.status === 'complete' && status.type === 'string') {
  console.log('final status:', status.value);
}

Read Partial String Content

const title = parser.getByPath('/title');

if (title?.type === 'string') {
  renderTitle(title.value, { final: title.status === 'complete' });
}

Detect End Of Document

parser.finish();

if (parser.root?.status === 'complete') {
  const value = materialize(parser.root);
}

Troubleshooting

parser.root is null. No root node has been created yet. Push a non-whitespace chunk first.

A number has value: null. The number is still streaming. Use raw for display, or wait until status === 'complete'.

A boolean or null node is pending. The literal has not resolved yet. For example, tr may become true.

resolve(state) returns undefined. The pull-style state does not currently contain a complete resolvable value. Call finish() when the stream ends and check state.error.

Invalid JSON produces an error. This is expected. The parser tolerates truncation, not malformed syntax.

API Reference

// Push-style
createPartialJsonParser(): PartialJsonParser

interface PartialJsonParser {
  push(chunk: string): ParseEvent[];
  finish(): ParseEvent[];
  readonly root: JsonNode | null;
  getByPath(path: string): JsonNode | null;
}

materialize(node: JsonNode): unknown;

// Pull-style
create(): StreamState;
push(state: StreamState, chunk: string): StreamState;
finish(state: StreamState): StreamState;
resolve(state: StreamState): JsonValue | undefined;

// Type guards
isObjectNode(node): node is JsonObjectNode;
isArrayNode(node): node is JsonArrayNode;
isStringNode(node): node is JsonStringNode;
isNumberNode(node): node is JsonNumberNode;
isBoolNode(node): node is JsonBooleanNode;
isNullNode(node): node is JsonNullNode;
isComplete(node): boolean;

Exported types:

JsonNode, JsonValue, StreamStatus, StreamState, StreamError, StreamErrorCode, ParseEvent,
JsonObjectNode, JsonArrayNode, JsonStringNode, JsonNumberNode,
JsonBooleanNode, JsonNullNode, JsonNodeType,
AstNode, ObjectNode, ArrayNode, StringNode, NumberNode, BoolNode, NullNode

Changelog

See CHANGELOG.md.

License

MIT

版本与发布记录

@cacheplane/partial-json 共发布 7 个版本,首次发布于 2026/05/03;过去 12 个月发布了 7 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.1latest2026/10/011 个月前
0.3.02026/09/022 个月前
0.2.22026/08/122 个月前
0.2.12026/05/066 个月前
0.2.02026/05/066 个月前
0.1.12026/05/036 个月前

关于 @cacheplane/partial-json 的常见问题

@cacheplane/partial-json 是什么?

Streaming partial-JSON parser with identity preservation, push/pull APIs, JSON Pointer lookups, and structural-sharing materialization.

如何安装 @cacheplane/partial-json?

在项目目录运行 npm install @cacheplane/partial-json,或使用 pnpm add / yarn add / bun add。

@cacheplane/partial-json 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cacheplane/partial-json 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@cacheplane/partial-json 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cacheplane/partial-json 可以商用吗?

@cacheplane/partial-json 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cacheplane/partial-json 的下载量有多少?

根据 npm 官方下载统计,@cacheplane/partial-json 在 2026/10/09 – 2026/11/07 这 30 天内被下载 7,307 次,日均约 244 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cacheplane/partial-json 还在维护吗?

最新版本 0.3.1 发布于 2026/10/01(1 个月前);至今共发布 7 个版本,其中过去 12 个月发布了 7 个;首次发布于 2026/05/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。