canonical-json
v0.4.0
Published
a canonical json implementation
Maintainers
Readme
canonical-json - Deterministic JSON.stringify()
A drop-in replacement for JSON.stringify that produces deterministic, canonical JSON output compliant with RFC 8785 (JSON Canonicalization Scheme).
Ideal for content-addressable hashing, digital signatures, and distributed systems where identical objects must produce identical bytes.
Install
npm install canonical-jsonUsage
import stringify from 'canonical-json'
stringify({ b: 2, a: 1, c: { y: 0, x: 9 } })
// '{"a":1,"b":2,"c":{"x":9,"y":0}}'Replacer
Supports both function and array replacers, just like JSON.stringify:
stringify({ a: 1, b: 2, c: 3 }, ['a', 'c'])
// '{"a":1,"c":3}'
stringify({ a: 1, b: 2 }, (key, value) => key === 'b' ? undefined : value)
// '{"a":1}'Indentation
stringify({ a: 1 }, null, 2)
// '{\n "a": 1\n}'Custom Key Order
Pass a comparator function as the fourth argument:
const order = { first: 1, second: 2, third: 3 }
const cmp = (a, b) => (order[a] || 9999) - (order[b] || 9999)
stringify({ third: 'c', first: 'a', second: 'b' }, null, null, cmp)
// '{"first":"a","second":"b","third":"c"}'Streaming with walk
walk traverses the value and streams canonical JSON chunks to a callback — without building the full string:
import { createHash } from 'node:crypto'
import { walk } from 'canonical-json'
const hasher = createHash('sha256')
walk(obj, chunk => hasher.update(chunk))
const digest = hasher.digest('hex')Convenience hashing
For Node.js/Bun/Deno, a separate entry point provides one-liner hashing without pulling node:crypto into browser builds:
import { hash } from 'canonical-json/hash'
hash(obj) // sha256 hex digest
hash(obj, 'md5') // md5 hex digest
hash(obj, 'sha512') // sha512 hex digestAPI
// canonical-json
export default function stringify(
value: any,
replacer?: ((key: string, value: any) => any) | string[],
space?: string | number,
keyCompare?: (a: string, b: string) => number
): string | undefined
export function walk(
value: any,
write: (chunk: string) => void,
keyCompare?: (a: string, b: string) => number
): void
// canonical-json/hash (Node.js/Bun/Deno only)
export function hash(
value: any,
algorithm?: string, // default: 'sha256'
keyCompare?: (a: string, b: string) => number
): string // hex digestRFC 8785 Compliance
When called without replacer, space, or keyCompare, the output conforms to RFC 8785 (JCS):
- Object keys sorted by UTF-16 code unit order
- Numbers serialized per ES6
Number.toString()rules (-0becomes0) - Only mandatory characters escaped (
U+0000-U+001F,",\) - No whitespace
CLI
# Canonicalize
echo '{"b":2,"a":1}' | canonical-json
# {"a":1,"b":2}
# Content hash (default sha256)
echo '{"b":2,"a":1}' | canonical-json hash
# 43258cff783fe7036d8a43033f830adfc60ec037...
# Hash with specific algorithm
echo '{"b":2,"a":1}' | canonical-json hash --algo md5
# Hash multiple files
canonical-json hash *.json
# Verify files are canonical (exit 1 if not)
canonical-json verify data.json
canonical-json verify *.jsonTest
npm testZero dependencies. Tests use Node's built-in test runner.
License
MIT
