Skip to content

Instantly share code, notes, and snippets.

@colelawrence
Last active March 5, 2025 14:11
Show Gist options
  • Select an option

  • Save colelawrence/3349ac4218d25e7c997a7d2bd31193d0 to your computer and use it in GitHub Desktop.

Select an option

Save colelawrence/3349ac4218d25e7c997a7d2bd31193d0 to your computer and use it in GitHub Desktop.
/**
* Check the equality of anything
*
* **Example return**
*
* { "permissions.foo": [{}, { "a": 1 }] }
*
* @param {any} obj1
* @param {any} obj2
* @returns {{ [path: string]: [any, any] }} list of differences in tuple [<path>, <a>, <b>]
*/
function deepDiff(obj1, obj2) {
return deepDiffR(obj1, obj2, []).reduce(
(acc, [path, a, b]) => ({
...acc,
[path.join('.')]: [a, b],
}),
{},
);
}
/**
* @internal
* Check the equality of anything
* @param {any} obj1
* @param {any} obj2
* @param {(string|number|symbol)[]} path
* @returns {[(string|number|symbol)[], any, any][]} list of differences in tuple [<path>, <a>, <b>]
*/
function deepDiffR(obj1, obj2, path) {
if (obj1 === obj2) {
return [];
}
if (isObject(obj1) && isObject(obj2)) {
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return [[path, obj1, obj2]];
}
const diffs = [];
for (const prop in obj1) {
diffs.push(...deepDiffR(obj1[prop], obj2[prop], [...path, prop]));
}
return diffs;
}
return [[path, obj1, obj2]];
}
/**
* @internal
* @param {any} obj
* @returns {obj is {[key: string | number | symbol]: any}}
*/
function isObject(obj) {
return typeof obj === 'object' && obj != null;
}
/**
* Check the equality of anything
*
* **Example return**
*
* { "permissions.foo": [{}, { "a": 1 }] }
*
* @returns list of differences in tuple [<path>, <a>, <b>]
*/
export function deepDiff(obj1: any, obj2: any): { [path: string]: [any, any] } {
return deepDiffR(obj1, obj2, []).reduce(
(acc, [path, a, b]) => ({
...acc,
[path.join(".")]: [a, b],
}),
{}
);
}
/**
* @internal
* Check the equality of anything
* @returns list of differences in tuple [<path>, <a>, <b>]
*/
function deepDiffR(
obj1: any,
obj2: any,
path: (string | number | symbol)[]
): [(string | number | symbol)[], any, any][] {
if (obj1 === obj2) {
return [];
}
if (isObject(obj1) && isObject(obj2)) {
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return [[path, obj1, obj2]];
}
const diffs: ReturnType<typeof deepDiffR> = [];
for (const prop in obj1) {
diffs.push(...deepDiffR(obj1[prop], obj2[prop], [...path, prop]));
}
return diffs;
}
return [[path, obj1, obj2]];
}
/**
* @internal
*/
function isObject(
obj: any
): obj is { [key: string]: any; [keyN: number]: any } {
return typeof obj === "object" && obj != null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment