tabnas-json (Rust)
Standard JSON grammar plugin for the
tabnas parsing engine, crate
tabnas_json.
The engine ships no grammar of its own; this crate supplies the strict,
standard-JSON one. The rule set (val / map / list / pair /
elem) is jsonic's "Plain JSON" grammar, installed on its own with the
lexer restricted to strict JSON: double-quoted strings, plain decimal
numbers, quoted keys, no comments, no trailing commas.
This is the Rust port of the canonical TypeScript implementation in
../ts; the TypeScript version is authoritative and this crate
tracks it.
Documentation
Full Diátaxis docs:
doc/tutorial.md. Learn it step by step.doc/guide.md. Task-focused recipes.doc/reference.md. The exact API surface.doc/concepts.md. How it works, including the differences from the TypeScript version.
TypeScript is canonical; its docs are in ../ts/doc/, and
the Go port has the equivalent set in ../go/doc/.
Use
Or build an instance and reuse it:
To layer another grammar on the JSON core, install the plugin on your own
instance and add rules on top of the shared val / map / list /
pair / elem:
Install
The tabnas crate is not published to a registry, so the engine is
consumed as a sibling checkout, the standard tabnas development
model. Clone https://github.com/tabnas/parser next to this repository
and point at it:
[]
= { = "../json/rs" }
= { = "../parser/rs" }
Both entries are needed. A crate's dependencies are not passed on to its
dependents, so tabnas-json alone does not put tabnas in your extern
prelude, and the examples above that name tabnas::Tabnas would not
resolve. Only JsonError is re-exported.
Differences from the canonical TypeScript
Four, all deliberate:
- Out-of-range exponents are rejected.
1e999is syntactically valid JSON, and the platform parsers disagree about it:JSON.parsesaturates toInfinity, whileserde_jsonandencoding/jsonboth error. This package is held to per-runtime parity, so Rust rejects with Go rather than accepting with TypeScript. Underflow (1e-999to0) is accepted, as it is in Go. - Nesting past 127 levels is rejected.
serde_jsonaccepts 127 and refuses one level deeper, whileJSON.parseandencoding/jsonboth go much further, so this port follows its own platform again. It is also what keeps a deeply nested source from ending the process: without the limit, a kilobyte of open brackets overflows the stack instead of returning an error. - Integer-like object keys keep document order. A JavaScript object
enumerates them first, in ascending numeric order, so TypeScript reads
{"2":"a","1":"b"}back as1,2where this crate gives2,1. AnIndexMapdoes not reorder, and neither doesserde_json. Non-index keys keep insertion order in both. - Strictness is a
checkhook, notnumber.exclude. The TypeScript exclude is a negative lookahead, and theregexcrate has no lookaround, so the positive pattern plus an inversion is the only way to express it here. That is the shape the Go port already uses.
Build and test
The engine is a path dependency on the sibling checkout, so there is nothing to fetch:
Or, from the repository root, make test-rs. For what CI would say,
including formatting and the lockfile check, run ci/rust/run.sh.
The suite runs the shared ../test/spec/*.tsv conformance fixtures, the
same files the TypeScript and Go suites run, and additionally
cross-checks every valid row against serde_json, this runtime's
platform oracle, the way the Go runner checks against encoding/json.
It also grades the external
nst/JSONTestSuite corpus at a
pinned commit, fetching it on first use. Every one of the 283 cases
RFC 8259 makes mandatory passes. Eleven of the 35 implementation-defined
cases differ from serde_json, each named with its reason in the test;
ten are lone surrogates in a \u escape, which this parser turns into
U+FFFD rather than rejecting, and one is a 48-digit integer where
serde_json is a unit in the last place away from what the Rust standard
library returns.
License
MIT.