/* A small structural diff of two JSON values: objects by key, arrays by index. A leaf that differs, a key present on one side only, and a change of type each give one entry. */ export interface JsonDifference { path: string a: unknown b: unknown } const kind = (v: unknown): string => (v === null ? 'null' : Array.isArray(v) ? 'array' : typeof v) export function jsonDiff(a: unknown, b: unknown, path = ''): JsonDifference[] { const ka = kind(a) if (ka !== kind(b)) return [{ path, a, b }] if (ka === 'array') { const [xs, ys] = [a as unknown[], b as unknown[]] const out: JsonDifference[] = [] for (let i = 0; i < Math.max(xs.length, ys.length); i++) { const here = `${path}[${i}]` if (i >= xs.length || i >= ys.length) out.push({ path: here, a: xs[i], b: ys[i] }) else out.push(...jsonDiff(xs[i], ys[i], here)) } return out } if (ka === 'object') { const [x, y] = [a as Record, b as Record] const out: JsonDifference[] = [] for (const key of new Set([...Object.keys(x), ...Object.keys(y)])) { const here = path === '' ? key : `${path}.${key}` const [inX, inY] = [Object.hasOwn(x, key), Object.hasOwn(y, key)] if (inX && inY) out.push(...jsonDiff(x[key], y[key], here)) else out.push({ path: here, a: x[key], b: y[key] }) } return out } return a === b || (Number.isNaN(a) && Number.isNaN(b)) ? [] : [{ path, a, b }] }