node assert.equal
This is probably more reliable than either of these.
recursive implementation
The main drawback with the recursive implementation is that it will cause a stack overflow with large objects. Simply pass it an array with 30,000 elements and watch it crash. I checked deno's deep equal implementation and it also suffers from this. I don't know if node's has this issue.
function deepEqual(x, y) {
let tx = typeof x;
let ty = typeof y;
if (tx !== "object" && ty !== "object") {
// Early exit for non-objects.
if (x === y) {
return true;
}
} else if (x === null || y === null) {
// Check for typeof(null) = "object" edgecase.
if (x === y) {
return true;
} else {
return false; // Avoid object checking logic.
}
} else {
// Both are non-null objects. Compare keys.
let xkeys = Object.keys(x);
let ykeys = Object.keys(y);
if (xkeys.length !== ykeys.length) {
// Early exit for differing key lengths.
return false;
}
// Walk keys recursively.
for (let key of xkeys) {
if (!ykeys.includes(key) || !deepEqual(x[key], y[key])) return false;
}
return true; // All keys matched.
}
return false;
}
iterative implementation
This one is a bit more complicated, but can handle massive objects. I'm not sure how the speed compares to the recursive implementation.
function deepEqual(x, y) {
let tx = typeof x;
let ty = typeof y;
// Early exit for primitive equality.
if (x === y) {
return true;
}
// Handle null and non-object cases.
if (x === null || y === null || tx !== "object" || ty !== "object") {
return false;
}
// Create iteration stack and compare objects.
let xstack = [x];
let ystack = [y];
for (let i = 0; i < xstack.length; i++) {
// Check current stack objects.
let xx = xstack[i];
let yy = ystack[i];
let xkeys = Object.keys(xx);
let ykeys = Object.keys(yy);
if (xkeys.length !== ykeys.length) {
// Key length mismatch early exit.
return false;
}
// Check if we need to search sub-children.
for (let ii = 0; ii < xkeys.length; ii++) {
let key = xkeys[ii];
if (!yy.hasOwnProperty(key)) {
// Easier than .includes on keys
return false;
}
let xchild = xx[key];
let ychild = yy[key];
if (xchild !== ychild) {
// Handle null and non-object cases.
if (
xchild === null ||
ychild === null ||
typeof xchild !== "object" ||
typeof ychild !== "object"
) {
return false;
}
// Add children to the stacks.
xstack.push(xchild);
ystack.push(ychild);
}
}
}
return true; // Search completed without inequality.
}