function isJsonObject(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function canonicalizeJson(value: unknown): unknown { if (Array.isArray(value)) return value.map(canonicalizeJson); if (!isJsonObject(value)) return value; const output: Record = {}; for (const key of Object.keys(value).sort()) { const item = value[key]; if (item !== undefined) output[key] = canonicalizeJson(item); } return output; } export function stableCanonicalJson(value: unknown): string { return JSON.stringify(canonicalizeJson(value)); } export function jsonDeepEqual(left: unknown, right: unknown): boolean { if (left === right) return true; if (Array.isArray(left) || Array.isArray(right)) { if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false; return left.every((item, index) => jsonDeepEqual(item, right[index])); } if (isJsonObject(left) || isJsonObject(right)) { if (!isJsonObject(left) || !isJsonObject(right)) return false; const leftKeys = Object.keys(left).sort(); const rightKeys = Object.keys(right).sort(); if (leftKeys.length !== rightKeys.length) return false; for (let index = 0; index < leftKeys.length; index += 1) { const leftKey = leftKeys[index]; const rightKey = rightKeys[index]; if (leftKey === undefined || rightKey === undefined || leftKey !== rightKey) return false; if (!jsonDeepEqual(left[leftKey], right[rightKey])) return false; } return true; } return false; }