How to Determine Equality For Two JavaScript Objects

How to determine equality for two JavaScript objects?

The short answer

The simple answer is: No, there is no generic means to determine that an object is equal to another in the sense you mean. The exception is when you are strictly thinking of an object being typeless.

The long answer

The concept is that of an Equals method that compares two different instances of an object to indicate whether they are equal at a value level. However, it is up to the specific type to define how an Equals method should be implemented. An iterative comparison of attributes that have primitive values may not be enough: an object may contain attributes which are not relevant to equality. For example,

 function MyClass(a, b)
{
var c;
this.getCLazy = function() {
if (c === undefined) c = a * b // imagine * is really expensive
return c;
}
}

In this above case, c is not really important to determine whether any two instances of MyClass are equal, only a and b are important. In some cases c might vary between instances and yet not be significant during comparison.

Note this issue applies when members may themselves also be instances of a type and these each would all be required to have a means of determining equality.

Further complicating things is that in JavaScript the distinction between data and method is blurred.

An object may reference a method that is to be called as an event handler, and this would likely not be considered part of its 'value state'. Whereas another object may well be assigned a function that performs an important calculation and thereby makes this instance different from others simply because it references a different function.

What about an object that has one of its existing prototype methods overridden by another function? Could it still be considered equal to another instance that it otherwise identical? That question can only be answered in each specific case for each type.

As stated earlier, the exception would be a strictly typeless object. In which case the only sensible choice is an iterative and recursive comparison of each member. Even then one has to ask what is the 'value' of a function?

Object comparison in JavaScript

Unfortunately there is no perfect way, unless you use _proto_ recursively and access all non-enumerable properties, but this works in Firefox only.

So the best I can do is to guess usage scenarios.


1) Fast and limited.

Works when you have simple JSON-style objects without methods and DOM nodes inside:

 JSON.stringify(obj1) === JSON.stringify(obj2) 

The ORDER of the properties IS IMPORTANT, so this method will return false for following objects:

 x = {a: 1, b: 2};
y = {b: 2, a: 1};

2) Slow and more generic.

Compares objects without digging into prototypes, then compares properties' projections recursively, and also compares constructors.

This is almost correct algorithm:

function deepCompare () {
var i, l, leftChain, rightChain;

function compare2Objects (x, y) {
var p;

// remember that NaN === NaN returns false
// and isNaN(undefined) returns true
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
return true;
}

// Compare primitives and functions.
// Check if both arguments link to the same object.
// Especially useful on the step where we compare prototypes
if (x === y) {
return true;
}

// Works in case when functions are created in constructor.
// Comparing dates is a common scenario. Another built-ins?
// We can even handle functions passed across iframes
if ((typeof x === 'function' && typeof y === 'function') ||
(x instanceof Date && y instanceof Date) ||
(x instanceof RegExp && y instanceof RegExp) ||
(x instanceof String && y instanceof String) ||
(x instanceof Number && y instanceof Number)) {
return x.toString() === y.toString();
}

// At last checking prototypes as good as we can
if (!(x instanceof Object && y instanceof Object)) {
return false;
}

if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
return false;
}

if (x.constructor !== y.constructor) {
return false;
}

if (x.prototype !== y.prototype) {
return false;
}

// Check for infinitive linking loops
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
return false;
}

// Quick checking of one object being a subset of another.
// todo: cache the structure of arguments[0] for performance
for (p in y) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
else if (typeof y[p] !== typeof x[p]) {
return false;
}
}

for (p in x) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
else if (typeof y[p] !== typeof x[p]) {
return false;
}

switch (typeof (x[p])) {
case 'object':
case 'function':

leftChain.push(x);
rightChain.push(y);

if (!compare2Objects (x[p], y[p])) {
return false;
}

leftChain.pop();
rightChain.pop();
break;

default:
if (x[p] !== y[p]) {
return false;
}
break;
}
}

return true;
}

if (arguments.length < 1) {
return true; //Die silently? Don't know how to handle such case, please help...
// throw "Need two or more arguments to compare";
}

for (i = 1, l = arguments.length; i < l; i++) {

leftChain = []; //Todo: this can be cached
rightChain = [];

if (!compare2Objects(arguments[0], arguments[i])) {
return false;
}
}

return true;
}

Known issues (well, they have very low priority, probably you'll never notice them):

  • objects with different prototype structure but same projection
  • functions may have identical text but refer to different closures

Tests: passes tests are from How to determine equality for two JavaScript objects?.

How to determine equality for two JavaScript objects?

The short answer

The simple answer is: No, there is no generic means to determine that an object is equal to another in the sense you mean. The exception is when you are strictly thinking of an object being typeless.

The long answer

The concept is that of an Equals method that compares two different instances of an object to indicate whether they are equal at a value level. However, it is up to the specific type to define how an Equals method should be implemented. An iterative comparison of attributes that have primitive values may not be enough: an object may contain attributes which are not relevant to equality. For example,

 function MyClass(a, b)
{
var c;
this.getCLazy = function() {
if (c === undefined) c = a * b // imagine * is really expensive
return c;
}
}

In this above case, c is not really important to determine whether any two instances of MyClass are equal, only a and b are important. In some cases c might vary between instances and yet not be significant during comparison.

Note this issue applies when members may themselves also be instances of a type and these each would all be required to have a means of determining equality.

Further complicating things is that in JavaScript the distinction between data and method is blurred.

An object may reference a method that is to be called as an event handler, and this would likely not be considered part of its 'value state'. Whereas another object may well be assigned a function that performs an important calculation and thereby makes this instance different from others simply because it references a different function.

What about an object that has one of its existing prototype methods overridden by another function? Could it still be considered equal to another instance that it otherwise identical? That question can only be answered in each specific case for each type.

As stated earlier, the exception would be a strictly typeless object. In which case the only sensible choice is an iterative and recursive comparison of each member. Even then one has to ask what is the 'value' of a function?

How to check equality of two objects, one object's attribute's type is number other ones type is string but they are numbers

You can use _.isEqualWith:

var object1 = {  
x1: "1.000000",
x2: undefined,
x3: "1.0",
x4: "1.0"
};
var object2 = {
x1: 1,
x2: undefined,
x3: 1,
x4: 1
};

const cmpStr2Num = (val1, val2) => {
if ((typeof val1 === 'string' && typeof val2 === 'number')
|| (typeof val2 === 'string' && typeof val1 === 'number'))
return Number(val1) === Number(val2)
}

console.log(_.isEqualWith(object1, object2, cmpStr2Num))
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

How to check equality of two objects with selected attributes but they are different types

Define an array of keys that you want to check the equality for, then use .every() on that array to iterate over each key. For each key, you can check if its value from point1 is numeric using isNaN(), and if it is, convert it to a number using the unary plus operator (+), otherwise, you can leave it as it's original value and compare this against the value at the key stored in point2. The callback to .every() needs to return true for all keys in your array for pointsEqual to be true, otherwise it will be false if there is a mismatch between two keys:

const point1 = { id: 1234, p1: "1.000000", p2: undefined, p3: "1.0", p4: "1.0", p5: "somevale 1" };
const point2 = { id: 3456, p1: 1, p2: undefined, p3: 1, p4: 1, p5: "somevalue 2" };

const keys = ["p1", "p2", "p3", "p4"];
const pointsEqual = keys.every(
key => (isNaN(point1[key]) ? point1[key] : +point1[key]) === point2[key]
);
console.log(pointsEqual); // true

Comparing an two objects with arrays for quality

I fixed it using the following function, this will only work if the two objects have the same structure.

const _ = require("lodash");

const filtersA = {
brands: ["5f1d5077b261aa63f42cc8a9", "5f1d5077b261aa63f42cc8aa"],
models: [],
};

const filtersB = {
brands: ["5f1d5077b261aa63f42cc8aa", "5f1d5077b261aa63f42cc8a9"],
models: [],
};

/**
* Checks if two objects with arrays are equal
*
* @param {Object} a
* @param {Object} b
*/
const areObjectWithArraysEqual = (a, b) => Object.keys(a).every((key) => _.isEqual(a[key].sort(), b[key].sort()));

console.log(areObjectWithArraysEqual(filtersA, filtersB));

This returns true as expected



Related Topics



Leave a reply



Submit