Skip to content

Parsing JSON

Package: @lanexio/parser-grammar-json Stable Layer: 2 (Grammar). Depends only on @lanexio/parser-core. Runtime: Universal (browser, server, edge worker).

parseJson implements a full iterative JSON parser conforming to RFC 8259 (also ECMA-404). It handles objects, arrays, strings, numbers, booleans, and null with a single-pass explicit-container-stack state machine. No recursion.

Input is always a Uint8Array. Output is always a LexTree. Malformed JSON produces JsonKind.Error nodes — parseJson never throws.

The parser passes all 375 JSONTestSuite cases: 95 valid (y_*), 183 invalid/rejected (n_*), and 33 never-throw (i_*).

import {
parseJson,
JsonKind,
JsonField,
JSON_KIND_NAMES_BY_ID,
jsonGrammar,
type ParseJsonOptions,
type JsonKindType,
} from '@lanexio/parser-grammar-json';
import { parseJson } from '@lanexio/parser-grammar-json';
const encoder = new TextEncoder();
const tree = parseJson(encoder.encode('{"key": [1, true, null]}'));
console.log(tree.nodeCount); // total nodes
console.log(tree.root.kind); // Document root kind id (0x0800)

parseJson accepts a Uint8Array. Always use TextEncoder when converting a string to bytes.

const tree = parseJson(encoder.encode(`
{
"users": [
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
]
}
`));
// Walks: Document → Object → Member("users") → Array → Object → Member("id", Number(1)), ...
// Valid — produces Document → True
parseJson(encoder.encode('true'));
// Invalid — produces Document → Error (never throws)
const tree = parseJson(encoder.encode('{broken'));
console.log(tree.root.child(0)?.kind === JsonKind.Error); // true
FieldTypeDefaultDescription
strictbooleanundefinedReserved for future use. Currently has no effect.
import { parseJson, JsonKind } from '@lanexio/parser-grammar-json';
const encoder = new TextEncoder();
const tree = parseJson(encoder.encode('[1,]')); // trailing comma — RFC 8259 disallows
for (const node of tree.root.children()) {
if (node.kind === JsonKind.Error) {
console.log('parse error at', node.range);
}
}

parseJson never throws. Invalid JSON produces JsonKind.Error nodes. The parser always recovers.

import { JsonKind } from '@lanexio/parser-grammar-json';
// JsonKind is a const object. Use 'as const' pattern, never enum.
const kind: JsonKindType = JsonKind.Array;
// Node kind IDs (0x0800 block)
JsonKind.Document; // 0x0800
JsonKind.Object; // 0x0801
JsonKind.Member; // 0x0802
JsonKind.Array; // 0x0803
JsonKind.String; // 0x0804
JsonKind.Number; // 0x0805
JsonKind.True; // 0x0806
JsonKind.False; // 0x0807
JsonKind.Null; // 0x0808
JsonKind.Whitespace; // 0x0809
JsonKind.Error; // 0x080a

JsonKind values are stable across versions. Never use raw numbers — always reference JsonKind.<name>.

ExportTypeDescription
parseJson(bytes: Uint8Array, options?: ParseJsonOptions) => LexTreeParse JSON RFC 8259. Never throws.
ParseJsonOptions{ readonly strict?: boolean }Options for parseJson.
JsonKindconst objectNumeric kind IDs for all JSON node types (0x0800 block).
JsonKindTypetype unionUnion of all JsonKind values.
JSON_KIND_NAMES_BY_IDReadonly<Record<number, string>>Kind-name lookup by numeric ID.
jsonGrammarLanexio ParserPureGrammarGrammar descriptor for parser-pure.
MetricValue
JSONTestSuite y_ (valid)95 / 95
JSONTestSuite n_ (rejected)183 / 183
JSONTestSuite i_ (never-throw)33 / 33
Total375 / 375
verify:no-throw59 entry points
Fuzz soak (300s)11.7M rounds / 0 crashes