jstrict
A strict RFC 8259 / ECMA-404 JSON parser for Rust that keeps what the standard leaves in the document — entry order, duplicate keys, number precision — and tells you where every value came from.
use ;
let = parse_str?;
// Fragment 3 is the `0` bound to "a" — byte 7 of the source.
assert_eq!;
println!;
When you don't need the spans, take the fast path — it skips span tracking entirely:
let value = parse_str_value?;
Build values from a literal with json!:
use json;
let value = json!;
Why this fork
Fork of json-syntax by Timothée Haudebourg. Semantics are unchanged — same strictness, same Value, same CodeMap. This fork adds:
- A byte-slice parser behind
parse_str/parse_slice, replacing the char-iterator front end: strings and numbers are scanned byte-wise, un-escaped runs are copied as slices instead of pushed per char, andnull/true/falseare matched with a single 4/5-byte compare. - SWAR scanners (
u64-at-a-time) for control and escape bytes, with amemchr-accelerated"/\scan in the string path. - SIMD UTF-8 validation via
simdutf8(default-on) and SIMD string escaping viajson-escape-simdwhen printing. parse::parse_str_value/parse::parse_slice_value, which skip the code map. The parser is generic over its span recorder, so the no-op path monomorphises away rather than being branched around.- Object key index rebuilt on
hashbrown::HashTablewith cached key hashes, so growing the table does not re-hash. json-numberfolded in as thenumbermodule; number formatting and parsing go throughlexical.- Conversion from/to
sonic_rs::Value, alongside the existingserde_json::Valueconversion. - Criterion bench suite under
benches/— parse, print, access, object, serde, convert, canonicalize, numbers, strings, macros, end-to-end. - Rust 2024 edition, MSRV 1.96.
- Renamed crate:
json-syntax→jstrict.
Credit and history preserved — see Attribution.
Install
Feature flags
| Flag | Default | Enables |
|---|---|---|
simdutf8 |
yes | SIMD-accelerated UTF-8 validation in the slice parser |
canonicalize |
JSON Canonicalization Scheme, RFC 8785 | |
serde |
Serialize / Deserialize, plus to_value / from_value |
|
serde_json |
Value::from_serde_json / Value::into_serde_json |
|
sonic-rs |
Value::from_sonic_rs / Value::into_sonic_rs |
|
contextual |
contextual::WithContext printing adapters |
Code maps
Parsing returns a CodeMap alongside the value: a flat list of entries, one per fragment (a value, an object entry, or an object key), in the same depth-first order as Value::traverse. Each entry carries the fragment's byte span and its volume — the number of fragments it contains, itself included — so a subtree can be skipped in one step.
Nothing about the source layout is stored inside the value tree, so Value stays cheap to clone and compare. Object::iter_mapped and JsonArray::iter_mapped walk a value and its code map together, and TryFromJson uses that to report conversion errors against a span.
Duplicates and ordering
A JSON object here is a list of entries, not a map. Duplicate keys are preserved exactly as written, and { "a": 0, "b": 1 } is not equal to { "b": 1, "a": 0 } under the derived PartialEq. An internal index still gives O(1) average lookup.
get/get_entriesreturn an iterator over every match.get_uniqueand friends returnErr(Duplicate(..))when a key matches more than once.insertreplaces the first match and removes the rest;pushappends without touching duplicates.- For order-insensitive comparison, wrap in
Unorderedor callas_unordered().
Numbers
Numbers are stored in their lexical form, so 1E30 round-trips as 1E30 and precision is never silently lost. Number exposes range-checked as_i64 / as_u64, explicitly lossy as_f64_lossy, and as_f64_lossless, which returns None if the conversion would not round-trip.
With the canonicalize feature, Value::canonicalize rewrites a value into the JSON Canonicalization Scheme form of RFC 8785: keys sorted, numbers in shortest round-trip form. Use canonicalize_with in tight loops to reuse the float buffer.
Printing
use ;
let value = parse_str?.0;
println!; // one line, no spaces
println!; // one line, with spaces
println!; // multi-line, 2-space indent
let mut options = pretty;
options.indent = Tabs;
println!;
Options also controls the padding around every delimiter and the width or item count at which an array or object is expanded onto several lines.
Benchmarks
Criterion output: target/criterion/.
Examples
MSRV
Rust 1.96 (edition 2024).
Attribution
Original crate: json-syntax by Timothée Haudebourg. Upstream commits are preserved in this repo's history under their original authorship. This fork is a layer of performance work and interop on top of their design.
The test corpus is derived from JSONTestSuite by Nicolas Seriot.
License
Dual-licensed, same as upstream. Pick whichever fits: