Modern JSON parser extending JSON5 with ES2015–2025 features and year‑pinned APIs.
- Nine correctness fixes in the template-literal, tolerant-parsing, and reference-resolution paths —
mode: "json"/mode: "json5"now actually enforce their feature sets, tolerant mode reports the syntax errors it collects instead of a misleading reference error, forward-reference chains of any length resolve, andparseToAst()tokens/quasis tile the source with no gaps around template interpolations. - View full v1.1.1 Changelog
- v1.1.0 (September 2026) —
parseToAst()returns comments and tokens with positioned keys and corrected spans; reference-resolution errors now carry a position (Release) - v1.0.10 (September 2026) — parse errors expose
line,column, andoffset(Release) - v1.0.9 (September 2026) — README doc link for the Prettier plugin in the Tooling section (Release)
- v1.0.8 (September 2026) — README badge row, contributors/sponsor line, and license badges (Release)
@cldmv/jsonv is a static data format: JSON5 plus modern literals, with features gated by ECMAScript year modules. It adds internal references (file‑scoped, defined‑before‑use) and forbids executable syntax (no functions, classes, computed keys, or shorthand props).
- JSON5 superset (comments, trailing commas, single quotes, hex, etc.)
- Year‑pinned APIs:
@cldmv/jsonv/2011,/2015,/2020,/2021(2022–2025 re‑export 2021) - Modern literals: binary/octal, BigInt, numeric separators
- Internal references and template interpolation (ES2015+), including forward references
- Diagnostics:
diagnose()andinfo()for year + feature detection - Stringify with json/json5/jsonv modes, BigInt strategies, and raw JSON passthrough
- Dynamic year loading and resolver utilities (
loadYear,resolveYear) - Zero dependencies, hand‑written parser
npm install @cldmv/jsonvimport { parse, stringify } from "@cldmv/jsonv";
const config = parse(`{
port: 8080,
host: "localhost",
url: `http://${host}:${port}`,
maxConnections: 1_000_000,
bigValue: 9007199254740992n
}`);
const text = stringify(config);Pin a year for stable grammar rules:
import { parse } from "@cldmv/jsonv/2021"; // numeric separators + BigInt
import { parse as parse2015 } from "@cldmv/jsonv/2015"; // binary/octal + templates
import { parse as parse2011 } from "@cldmv/jsonv/2011"; // JSON5 baseSee docs/feature-matrix.md and docs/versioning-and-exports.md.
- docs/feature-matrix.md
- docs/versioning-and-exports.md
- docs/json5-compatibility.md
- docs/ast.md — AST, tokens and comments for tooling
Main entry: src/index.mts
year: 2011–2025 (defaults to latest)mode:jsonv(default) |json5(exactly JSON5 1.0) |json(exactly RFC 8259 JSON); see Parse modesallowInternalReferences: defaulttruestrictBigInt: requirenfor unsafe integers (defaultfalse)strictOctal: require0o(reject legacy0755, defaultfalse)tolerant: collect every syntax error instead of stopping at the first;parseWithOptionsthen throws them together as oneJsonvAggregateSyntaxError(see Errors)preserveComments: return comments (with positions) fromParser#parse(); see AST for tooling
mode: "json" accepts exactly RFC 8259 JSON and mode: "json5" accepts exactly JSON5 1.0; mode: "jsonv" (the default) enables every jsonv feature of the selected year. A feature outside the mode throws a positioned JsonvSyntaxError with code: "FEATURE_NOT_ALLOWED_IN_MODE" naming the feature and the mode, and an unknown mode value throws a TypeError. parse() takes the options object in place of the reviver:
import { parse } from "@cldmv/jsonv";
parse('{"a": [1, 2]}', { mode: "json" }); // { a: [1, 2] }
parse("{ a: 1, }", { mode: "json" }); // throws: Unquoted keys not allowed in JSON mode at line 1, column 2
parse("{ a: 1, b: a }", { mode: "json5" }); // throws: Internal references not allowed in JSON5 mode at line 1, column 11The full feature × mode table is in docs/json5-compatibility.md.
mode:jsonv | json5 | jsonbigint:native | string | objectsingleQuote,trailingComma,unquotedKeyspreserveNumericFormatting
Full types: src/api-types.mts
Parse failures throw JsonvSyntaxError (extends SyntaxError, name stays "SyntaxError"), with structured position info alongside the message:
import { parse, JsonvSyntaxError } from "@cldmv/jsonv";
try {
parse("{ a: 1, }");
} catch (err) {
if (err instanceof JsonvSyntaxError) {
console.log(err.line, err.column, err.offset); // 1-based line, message-matching column, 0-based offset
}
}This applies to every parse entry point (year-pinned APIs included) and every kind of positioned error — lexer-level (unterminated strings, invalid escapes, year-gated feature checks) and parser-level (unexpected tokens, strict-mode violations) alike.
With tolerant: true, the parser recovers at the next property or element boundary after a syntax error and keeps going. If any syntax error was collected, parseWithOptions (year-pinned APIs included) throws a single JsonvAggregateSyntaxError and does not evaluate the document. It is a JsonvSyntaxError whose own line/column/offset/code are the first error's, whose message is the first error's message followed by the total count, and whose errors array holds every error in source order, each a JsonvSyntaxError with its own position and code (the same error a strict parse would throw for it). A lexical error is reported through the same aggregate. Input without syntax errors evaluates exactly as it does without tolerant:
import { parseWithOptions, JsonvAggregateSyntaxError } from "@cldmv/jsonv";
try {
parseWithOptions("{ a: 1,, b: 2,, c: }", { tolerant: true });
} catch (err) {
if (err instanceof JsonvAggregateSyntaxError) {
err.message; // "Expected property key, got COMMA at line 1, column 7 (3 syntax errors in total)"
err.errors.map((e) => [e.line, e.column, e.code]); // [[1, 7, "PARSE_ERROR"], [1, 14, "PARSE_ERROR"], [1, 19, "PARSE_ERROR"]]
}
}parseToAst never throws for collected errors; it returns them in errors.
Internal-reference resolution failures (an unresolved or circular internal reference) throw the sibling JsonvReferenceError (extends ReferenceError, name stays "ReferenceError") instead, with the same structured line/column/offset/code shape, pointing at the offending reference:
import { parseWithOptions, JsonvReferenceError } from "@cldmv/jsonv";
try {
parseWithOptions("{ a: missing }");
} catch (err) {
if (err instanceof JsonvReferenceError) {
console.log(err.line, err.column, err.offset);
}
}parseToAst() returns the positioned AST without evaluating it, for linters, formatters and editors:
import { parseToAst } from "@cldmv/jsonv"; // also exported from "@cldmv/jsonv/parser"
const { program, comments, tokens, errors } = parseToAst("// port\n{ port: 8080 }");
program.body.properties[0].key; // { type: "Identifier", name: "port", loc: { start: { line: 2, column: 2, offset: 10 }, ... } }
comments[0].value; // " port"Every node, token and comment carries loc: { start, end } with { line, column, offset } positions (\n, \r\n, \r, U+2028 and U+2029 each count as one line break). Property keys are positioned Literal / Identifier nodes, and Property.loc spans key through value. parseToAst() never throws for invalid input: lexical and parse errors are both collected in errors (with code, line, column and offset), and tolerant: true recovers from both and reports every one. See docs/ast.md for the node reference.
{ port: 8080, backup: port, url: `http://${host}:${port}` }
Rules: file‑scoped only, forward references supported, no circular refs.
import { loadYear, getLoadedYear } from "@cldmv/jsonv/loader";
import { resolveYear, isPublishedYear, getPublishedYears } from "@cldmv/jsonv/year-resolver";
const jsonv2023 = await loadYear(2023); // resolves to 2021
const resolved = getLoadedYear(2017); // 2015
const published = getPublishedYears(); // [2011, 2015, 2020, 2021]
const isPublished = isPublishedYear(2021); // true
const nearest = resolveYear(2024); // 2021diagnose() returns detected year/features + compatibility flags (json, json5).
info() returns only detected year + parsed value.
- Test runner:
npm test(Vitest) - Fixtures: tests/fixtures/ with
features/andviolations/per year - See tests/fixtures/README.md for layout
- ESLint plugin: published separately as
@cldmv/eslint-plugin-jsonv(this repo's lint config consumes the published package). For local co-development, clone that repo under the gitignoredplugins/eslint-plugin-jsonv/path and runnpm run build:pluginto link it against this repo's current build. - Prettier plugin: published separately as
@cldmv/prettier-plugin-jsonvfor formatting.jsonvfiles. - VS Code language support: published separately as
jsonv-vscode; clone under the gitignoredplugins/vscode-jsonv/for local co-development.
npm run dev # uses src/ via json-dev condition
npm run build # clean → ts → types → years → cjs → plugin
npm test # Vitest
npm run lint # ESLint v9 configApache-2.0 © Shinrai / CLDMV