Deterministic JSON serializer for JSON-like values that canonicalizes object key order and preserves JSON.stringify value semantics. Key ordering uses lexicographic string code-unit order; for string keys this matches Object.keys(obj).sort().
pnpm add @escapace/canonicalizeimport { canonicalize } from '@escapace/canonicalize'
const value = {
b: 2,
a: 1,
list: [{ y: 2, x: 1 }],
}
const output = canonicalize(value)
// '{"a":1,"b":2,"list":[{"x":1,"y":2}]}'When the return value is a string, the output is valid JSON and JSON.parse(output) succeeds.
For supported domains, value-handling semantics follow JSON.stringify, including:
- primitives and boxed primitives,
toJSON(key)lookup and invocation behavior,- own enumerable string-keyed properties (symbol keys are ignored),
- sparse arrays and
length-driven index traversal, Dateconversion throughDate.prototype.toJSON,- typed-array object-style serialization,
- circular-structure failure with
TypeError.
Values with no JSON representation — undefined, symbols, and functions — propagate differently
depending on where they appear:
- Root: returns
undefined. - Object property: omitted from output.
- Array element: becomes
"null".
canonicalize(undefined) // undefined
canonicalize({ keep: 1, drop: undefined }) // '{"keep":1}'
canonicalize([1, undefined, 2]) // '[1,null,2]'For TypeScript callers, null, boolean, number, and string inputs have a narrowed return
type of string. Object and bigint inputs remain string | undefined because their outcome
depends on toJSON dispatch at runtime.
- Object keys are emitted in canonical lexicographic order (string code-unit order).
- Replacer and spacing parameters are not part of the API.
Integer-like key ordering differs from JSON.stringify / Object.keys ordering:
JSON.stringify({ 2: 'b', 10: 'a' })
// '{"2":"b","10":"a"}'
canonicalize({ 2: 'b', 10: 'a' })
// '{"10":"a","2":"b"}'canonicalize(2n)
// throws TypeErrorIf BigInt.prototype.toJSON is defined, returned values from that hook are serialized, matching JSON behavior.
const value: { self?: unknown } = {}
value.self = value
canonicalize(value)
// throws TypeError: Converting circular structure to JSONWhen runtime support exists for JSON.rawJSON and JSON.isRawJSON, raw payload text is emitted from raw JSON values when value.rawJSON is a string.
When runtime support is not available, values are serialized with standard object/primitive rules.
function canonicalize ↗
Converts a value to a canonical JSON string with object keys in lexicographic order.
export declare function canonicalize(value: unknown): string | undefined| Parameter | Type | Description |
|---|---|---|
value |
unknown |
Value to serialize. |
A canonical JSON string, or undefined when the input has no JSON representation.
- TypeError When a
BigIntvalue is encountered withoutBigInt.prototype.toJSONdefined. - TypeError When the input contains a circular object reference.
Value semantics follow JSON.stringify for the supported domain, including toJSON lookup and invocation, boxed-primitive coercion, sparse-array traversal, Date conversion via Date.prototype.toJSON, and circular-structure detection.
Non-serializable values — undefined, symbols, and functions — yield undefined rather than a string. The enclosing container determines how that result propagates:
- At root, the function returns
undefined. - As an object property value, the property is omitted from output.
- As an array element, the element is serialized as
"null".
Two behaviors differ intentionally from JSON.stringify:
- Object keys are emitted in lexicographic string code-unit order, equivalent to
Object.keys(obj).sort(). Integer-like keys follow the same order rather than the numeric-first ordering thatJSON.stringifyapplies. - Replacer and spacing parameters are not supported.
The return type is narrowed for primitive inputs whose outcome is determined before any toJSON call: null, boolean, number, and string inputs return string; undefined and symbol inputs return undefined. Object and bigint inputs remain string | undefined because their outcome depends on toJSON dispatch, which TypeScript cannot inspect statically.