
https://github.com/lucianofedericopereira/json-canon
Lint, re-encode, and much more
Take JSON produced by anything — CPython json, pandas to_json, Nim std/json, JS, hand-edited files with comments and trailing commas — and emit one deterministic byte-stream. Then cmp, sha256, and git diff on the output actually mean something, no matter who produced the input.
Two independent implementations (Python and Nim) that are byte-identical by construction.
MIT
The short answer: big ints.
I treat numbers as string tokens so you can handle 30-digit integers without losing precision (avoiding floating-point errors). 4.50e1 to 45 and 4.0 to 4 directly via string manipulation.
RFC 8785 explicitly mandates the ECMA-262 (JavaScript) float-to-string rules, which are bounded by IEEE 754 double-precision variables.
Under RFC 8785, integers larger than 2^53 may lose precision or turn into exponential notation.
I guess the best way to add support is some kind of RFC 8785 canonicalizer, like what jcs does, in python, If someone is interested in RFC8785 or have improvement ideas I will try to implement them. Thanks
Added RFC 8785 / JCS mode (--jcs). Numbers are serialized by the ECMAScript Number.prototype.toString algorithm and object keys are sorted by UTF-16 code unit. Includes a from-scratch pure-Nim/pure-Python port of Ulf Adams' Ryu d2s (shortest round-tripping binary64), with its 128-bit power-of-five tables generated by tools/gen_ryu_tables.py. Validated byte-for-byte against node's String(x) over 10M+ random doubles (tools/fuzz_ryu.py).
JCS precision warnings. In --jcs mode, every number whose value changes when routed through binary64 (e.g. 9007199254740993 → …992, or overflow) is reported with its JSON path. --log FILE writes the report; -q/--quiet suppresses stderr warnings but requires --log (a precision change is never silently lost).
Full JSON5 unquoted keys (SPEC §1.2). Unicode identifier keys ({café:1, π:2, 中文:3}) and uXXXX-escaped keys. The ID_Start/ID_Continue ranges are generated once by tools/gen_id_tables.py and embedded in both implementations so classification can't drift between Unicode databases.
CBOR (--from cbor / --to cbor). RFC 8949 Core Deterministic Encoding (§4.2): shortest integer/float (16/32/64) encoding, bignum tags for integers beyond 64-bit, and §4.2.1 map-key ordering. The Nim half-precision codec is verified against Python over all 65 536 bit patterns; encode is byte-identical between implementations.
JSON Schema --format — opt-in assertion of the format vocabulary (date-time/date/time, email, hostname, ipv4/ipv6, uri, uuid, json-pointer, regex) via deterministic hand-written checks in both languages.
MessagePack --from/--to msgpack — deterministic encoding (smallest int/float/header; map keys ordered by encoded bytes) + decode; mirrors CBOR.
JSON Schema $anchor (#name refs) and unevaluatedProperties/ unevaluatedItems with annotation collection across allOf / anyOf / oneOf / if-then-else / $ref.
Housekeeping git init + .github/workflows/ci.yml running the full gate (mypy, pyright, nim check, both suites, parity, Ryu fuzz vs node) on push/PR. tools/check.sh now builds the CLI itself (no longer relies on a pre-built binary) and spans ~58 parity rows. Added CHANGELOG and a Nim-artifacts section to .gitignore. Documented out of scope: remote $ref/$dynamicRef, and full single-document streaming (NDJSON/json-seq already stream per record). Notes
tools/parity.sh now spans ~46 byte-parity rows; tools/check.sh runs the full gate (mypy --strict, pyright, nim check, both test suites, parity).