Skip to content

Instantly share code, notes, and snippets.

@westc
Created September 3, 2023 00:36
Show Gist options
  • Save westc/0b27f83cc767e4d83061f6a648be101e to your computer and use it in GitHub Desktop.
Save westc/0b27f83cc767e4d83061f6a648be101e to your computer and use it in GitHub Desktop.
Determines the difference between 2 simple values.
function diff(left, right) {
function recurse(left, right, leftSeen, rightSeen, path) {
if (left === right || (left !== left && right !== right)) return [];
// If the constructors are different or if left and right are null and
// undefined but they are not the same...
if (left.constructor !== right?.constructor || left == null) {
return [{left, right, path, message: 'The values are not of the same type.'}];
}
// If left and right are the same type of primitives...
let t = typeof left;
if (t !== 'object' && t !== 'function') {
return [{left, right, path, message: 'The values are not the same.'}];
}
// Determine if left and/or right were seen before and if so return the
// appropriate diffs.
const leftSeenIndex = leftSeen.indexOf(left);
const rightSeenIndex = rightSeen.indexOf(right);
if (leftSeenIndex >= 0 || rightSeenIndex >= 0) {
if (leftSeenIndex === rightSeenIndex) return [];
return [{
left,
right,
path,
message: leftSeenIndex < 0
? "The right side is recursive but the left side isn't."
: rightSeenIndex < 0
? "The left side is recursive but the right side isn't."
: "Both sides are recursive but refer to different values."
}];
}
// At this point they must be of the same type so first see if they are iterable.
let areIterable = false;
try {
for (const _ of left) break;
areIterable = true;
} catch(e){}
const diffs = [];
if (areIterable) {
const leftLen = left.length, rightLen = right.length;
if (leftLen !== rightLen) {
diffs.push({
left,
right,
path,
message: `The left side has a length of ${leftLen} while the right side has a length of ${rightLen}.`
});
}
for (let i = 0, l = Math.min(leftLen, rightLen); i < l; i++) {
diffs.push(...recurse(
left[i],
right[i],
[...leftSeen, left],
[...rightSeen, right],
[...path, i]
));
}
}
else {
for (const key of new Set(Object.keys(left).concat(Object.keys(right)))) {
if (Object.hasOwn(left, key)) {
if (Object.hasOwn(right, key)) {
diffs.push(...recurse(
left[key],
right[key],
[...leftSeen, left],
[...rightSeen, right],
[...path, key]
));
}
else {
diffs.push({
left,
right,
path,
message: `The left side has a ${JSON.stringify(key)} key but the right side doesn't.`
});
}
}
else {
diffs.push({
left,
right,
path,
message: `The right side has a ${JSON.stringify(key)} key but the left side doesn't.`
});
}
}
}
return diffs;
}
return recurse(left, right, [], [], []);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment