Last active
December 10, 2024 13:04
-
-
Save nicbell/6081098 to your computer and use it in GitHub Desktop.
JavaScript object deep comparison. Comparing x === y, where x and y are values, return true or false. Comparing x === y, where x and y are objects, returns true if x and y refer to the same object. Otherwise, returns false even if the objects appear identical. Here is a solution to check if two objects are the same.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Primitive Type Comparison | |
var a = 1; | |
var b = 1; | |
var c = a; | |
console.log(a == b); //true | |
console.log(a === b); //true | |
console.log(a == c); //true | |
console.log(a === c); //true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Object comparison | |
var a = { blah: 1 }; | |
var b = { blah: 1 }; | |
var c = a; | |
console.log(a == b); //false | |
console.log(a === b); //false | |
console.log(a == c); //true | |
console.log(a === c); //true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//How To Compare Object Values | |
var a = { blah: 1 }; | |
var b = { blah: 1 }; | |
var c = a; | |
var d = { blah: 2 }; | |
Object.compare = function (obj1, obj2) { | |
//Loop through properties in object 1 | |
for (var p in obj1) { | |
//Check property exists on both objects | |
if (obj1.hasOwnProperty(p) !== obj2.hasOwnProperty(p)) return false; | |
switch (typeof (obj1[p])) { | |
//Deep compare objects | |
case 'object': | |
if (!Object.compare(obj1[p], obj2[p])) return false; | |
break; | |
//Compare function code | |
case 'function': | |
if (typeof (obj2[p]) == 'undefined' || (p != 'compare' && obj1[p].toString() != obj2[p].toString())) return false; | |
break; | |
//Compare values | |
default: | |
if (obj1[p] != obj2[p]) return false; | |
} | |
} | |
//Check object 2 for any extra properties | |
for (var p in obj2) { | |
if (typeof (obj1[p]) == 'undefined') return false; | |
} | |
return true; | |
}; | |
console.log(Object.compare(a, b)); //true | |
console.log(Object.compare(a, c)); //true | |
console.log(Object.compare(a, d)); //false |
Best implementation that I have seen so far is in rambda's "equals" function.
The only thing that I think can be improved (although, I am not sure that this is the right direction) is when both arguments have a "function" type, we could convert the source code to strings and compare them...
Here is the code (I simply put everything into 1 file) from their source:
const _isArray = Array.isArray;
function type(input) {
const typeOf = typeof input;
if (input === null) {
return "Null";
} else if (input === undefined) {
return "Undefined";
} else if (typeOf === "boolean") {
return "Boolean";
} else if (typeOf === "number") {
return Number.isNaN(input) ? "NaN" : "Number";
} else if (typeOf === "string") {
return "String";
} else if (_isArray(input)) {
return "Array";
} else if (typeOf === "symbol") {
return "Symbol";
} else if (input instanceof RegExp) {
return "RegExp";
}
const asStr = input && input.toString ? input.toString() : "";
if (["true", "false"].includes(asStr)) return "Boolean";
if (!Number.isNaN(Number(asStr))) return "Number";
if (asStr.startsWith("async")) return "Async";
if (asStr === "[object Promise]") return "Promise";
if (typeOf === "function") return "Function";
if (input instanceof String) return "String";
return "Object";
}
function parseError(maybeError) {
const typeofError = maybeError.__proto__.toString();
if (!["Error", "TypeError"].includes(typeofError)) return [];
return [typeofError, maybeError.message];
}
function parseDate(maybeDate) {
if (!maybeDate.toDateString) return [false];
return [true, maybeDate.getTime()];
}
function parseRegex(maybeRegex) {
if (maybeRegex.constructor !== RegExp) return [false];
return [true, maybeRegex.toString()];
}
// main function is here
function equals(a, b) {
if (arguments.length === 1) return (_b) => equals(a, _b);
const aType = type(a);
if (aType !== type(b)) return false;
if (aType === "Function") {
return a.name === undefined ? false : a.name === b.name;
}
if (["NaN", "Undefined", "Null"].includes(aType)) return true;
if (aType === "Number") {
if (Object.is(-0, a) !== Object.is(-0, b)) return false;
return a.toString() === b.toString();
}
if (["String", "Boolean"].includes(aType)) {
return a.toString() === b.toString();
}
if (aType === "Array") {
const aClone = Array.from(a);
const bClone = Array.from(b);
if (aClone.toString() !== bClone.toString()) {
return false;
}
let loopArrayFlag = true;
aClone.forEach((aCloneInstance, aCloneIndex) => {
if (loopArrayFlag) {
if (
aCloneInstance !== bClone[aCloneIndex] &&
!equals(aCloneInstance, bClone[aCloneIndex])
) {
loopArrayFlag = false;
}
}
});
return loopArrayFlag;
}
const aRegex = parseRegex(a);
const bRegex = parseRegex(b);
if (aRegex[0]) {
return bRegex[0] ? aRegex[1] === bRegex[1] : false;
} else if (bRegex[0]) return false;
const aDate = parseDate(a);
const bDate = parseDate(b);
if (aDate[0]) {
return bDate[0] ? aDate[1] === bDate[1] : false;
} else if (bDate[0]) return false;
const aError = parseError(a);
const bError = parseError(b);
if (aError[0]) {
return bError[0]
? aError[0] === bError[0] && aError[1] === bError[1]
: false;
}
if (aType === "Object") {
const aKeys = Object.keys(a);
if (aKeys.length !== Object.keys(b).length) {
return false;
}
let loopObjectFlag = true;
aKeys.forEach((aKeyInstance) => {
if (loopObjectFlag) {
const aValue = a[aKeyInstance];
const bValue = b[aKeyInstance];
if (aValue !== bValue && !equals(aValue, bValue)) {
loopObjectFlag = false;
}
}
});
return loopObjectFlag;
}
return false;
}
module.exports = equals;
+1 like on the solution of @DLiblik posted above, which also covers dates and functions. Maybe post it in a separate github repository so it can be found more easily?
@edwinro - done - find the gist here: areEquivalent.js
Covers most cases (including the order of the key property if present)
function isEqual(obj1, obj2) {
function getType(obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
let type = getType(obj1);
// If the two items are not the same type, return false
if (type !== getType(obj2)) return false;
if (type === "array") return areArraysEqual();
if (type === "object") return areObjectsEqual();
if (type === "function") return areFunctionsEqual();
function areArraysEqual() {
// Check length
if (obj1.length !== obj2.length) return false;
// Check each item in the array
for (let i = 0; i < obj1.length; i++) {
if (!isEqual(obj1[i], obj2[i])) return false;
}
// If no errors, return true
return true;
}
function areObjectsEqual() {
if (Object.keys(obj1).length !== Object.keys(obj2).length) return false;
// Check each item in the object
for (let key in obj1) {
if (Object.prototype.hasOwnProperty.call(obj1, key)) {
if (!isEqual(obj1[key], obj2[key])) return false;
}
}
// If no errors, return true
return true;
}
function areFunctionsEqual() {
return obj1.toString() === obj2.toString();
}
function arePrimativesEqual() {
return obj1 === obj2;
}
return arePrimativesEqual();
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Always glad to see this old thing created in 2013 has got people talking and learning from each other.