JSON Canonicalizer For Stable Signing
Serialize JSON with sorted keys, strict numeric checks and cycle rejection so independent services compute identical signing payloads.
Code
export function canonicalizeJson(value: unknown): string {
return serialize(value, new Set<object>());
}
function serialize(value: unknown, stack: Set<object>): string {
if (value === null) return "null";
const type = typeof value;
if (type === "string") return JSON.stringify(value) as string;
if (type === "boolean") return value ? "true" : "false";
if (type === "number") {
if (!Number.isFinite(value)) throw new TypeError("non-finite numbers are not allowed");
return JSON.stringify(value) as string;
}
if (type === "undefined" || type === "function" || type === "symbol" || type === "bigint") {
throw new TypeError("value of type " + type + " is not valid JSON");
}
if (Array.isArray(value)) {
if (stack.has(value)) throw new TypeError("cyclic array");
stack.add(value);
const items = value.map((item) => serialize(item, stack));
stack.delete(value);
return "[" + items.join(",") + "]";
}
if (type === "object") {
const object = value as Record<string, unknown>;
const prototype = Object.getPrototypeOf(object);
if (prototype !== Object.prototype && prototype !== null) {
throw new TypeError("only plain objects can be canonicalized");
}
if (stack.has(object)) throw new TypeError("cyclic object");
stack.add(object);
const keys = Object.keys(object).sort();
const parts = keys.map((key) =>
(JSON.stringify(key) as string) + ":" + serialize(object[key], stack));
stack.delete(object);
return "{" + parts.join(",") + "}";
}
throw new TypeError("unsupported value");
}Report a problem with this exampleOpens a pre-filled issue on GitHub.