Array of object deep comparison with lodash
Asked Answered
A

6

37

I've 2 array of objects that I'd deeply compare with lodash

However, I've a prob with it:

> var x = [{a:1, b:2}, {c:3, d:4}];
> var y = [{b:2, a:1}, {d:4, c:3}];
> _.difference(x,y, _.isEqual);
[ { a: 1, b: 2 }, { c: 3, d: 4 } ]

How should I compare to see that both are equal?

Arborescent answered 6/5, 2016 at 6:24 Comment(1)
Array of arrays is possible to sort. Array of objects is not.Arborescent
H
62

You can make use of differenceWith() with an isEqual() comparator, and invoke isEmpty to check if they are equal or not.

var isArrayEqual = function(x, y) {
  return _(x).differenceWith(y, _.isEqual).isEmpty();
};

var result1 = isArrayEqual(
  [{a:1, b:2}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

var result2 = isArrayEqual(
  [{a:1, b:2, c: 1}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

document.write([
  '<div><label>result1: ', result1, '</label></div>',
  '<div><label>result2: ', result2, '</label></div>',
].join(''));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.js"></script>

UPDATE June 22, 2018

This update is in response to the comment below:

@ryeballar if any of the array is undefined it returns true. How would you solve this. Thanks in advance buddy

As stated in the differenceWith documentation:

The order and references of result values are determined by the first array.

This means that as long as all the items in the first array will match everything else in the second array, then the resulting array from the differenceWith invocation will be empty.

An alternative solution that truly solves the problem is to use xorWith() with the same chain of functions from the solution above.

var isArrayEqual = function(x, y) {
  return _(x).xorWith(y, _.isEqual).isEmpty();
};

var result1 = isArrayEqual(
  [{a:1, b:2}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

var result2 = isArrayEqual(
  [{a:1, b:2, c: 1}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}]
);

var result3 = isArrayEqual(
  [{a:1, b:2, c: 1}, {c:3, d:4}],
  [{b:2, a:1}, {d:4, c:3}, undefined]
);

console.log('result1:', result1);
console.log('result2:', result2);
console.log('result3:', result3);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Hebbel answered 6/5, 2016 at 6:48 Comment(8)
Thanks @Hebbel . Basically I really need to find diff between them, so _.difference should be just replaced with _.differenceWith with _.isEqual comparator.Arborescent
I've never seen syntax for _(foo) mentioned anywhere in lodash. Where is this in the docs?Quillan
@DonP You may refer to this part of the lodash documentation.Hebbel
@Hebbel if any of the array is undefined it returns true. How would you solve this. Thanks in advance buddyKiona
@UzumakiNaruto I added an update, this answer is already quite outdated.Hebbel
@Hebbel , could you show, how I can achieve _(x) with individually installed components, because I do not need the whole lodash? I found lodash.isequal (ie npm i --save lodash.isequal), lodash.xorwith, lodash.isequal, lodash.isempty, but I do not know what to install for _(x).Bulwerlytton
You can simply require each function and then use them as you would in a non-chained manner. isEmpty(xorWith(x, y, isEqual))Hebbel
How about IsEmpty(differenceWith(x,y,IsEqual)) && isEmpty(differenceWith(y,x,IsEqual))? Looks easier to write, regardless of whether it takes two iterations instead of one. Simplicity might make it faster for small cases anyway.Incised
F
32

Following @ryeballar answer, if you only want to import specific lodash methods you can use this notation:

import { isEmpty, isEqual, xorWith } from 'lodash';


export const isArrayEqual = (x, y) => isEmpty(xorWith(x, y, isEqual));

Florous answered 23/7, 2019 at 14:25 Comment(3)
nice solution, thanksArborescent
awesome thanks there is codesandbox for it codesandbox.io/s/cold-fog-30b7d1?file=%2Fsrc%2FApp.vueSqualid
💜 this answer; TY so much. Called mine isEquivalent, as you can actually pass any two arrays/objects/values/...Rosierosily
C
6

Both answers on above with xorWith & differenceWith do not take in count if the array has different length only if the current item is found in the second array.

var isArrayEqual = function(x, y) {
  return _(x).xorWith(y, _.isEqual).isEmpty();
};

var result = isArrayEqual(
  [{a:1, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);


console.log('result should be false:', result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

In that particular case, we also would have to compare both arrays length.

const isArrayEqual = function(x, y) {
  const isSameSize = _.size(x) === _.size(y);
  return isSameSize && _(x).xorWith(y, _.isEqual).isEmpty();
};

const result = isArrayEqual(
  [{a:1, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);


console.log('result should be false:', result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Curtiscurtiss answered 23/8, 2021 at 7:15 Comment(1)
Thanks for including this check. You can get false-positives with the others.Theisen
T
2

I prefer pure JS since i haven't got the patience to learn underscore or lodash. So i invent something i have been long dreaming of. The Object.prototype.compare(). The v0.0.2 is doing only shallow comparison though but adequate for this question.

Object.prototype.compare = function(o){
  var ok = Object.keys(this);
  return typeof o === "object" && ok.length === Object.keys(o).length ? ok.every(k => this[k] === o[k]) : false;
};
var obj1 = {a:1,b:2,c:3},
    obj2 = {c:3,a:1,b:2},
    obj3 = {b:2,c:3,a:7};

document.write ("<pre>" + obj1.compare(obj2) + "</pre>\n");
document.write ("<pre>" + obj2.compare(obj3) + "</pre>\n");
document.write ("<pre>" + new Object({a:1, b:2, c:3}).compare({c:3,b:2,a:1,d:0}) + "</pre>\n");

Cool... So then lets continue with the question... I guess... since we already have an Object.prototype.compare() there should be absolutely no harm in the invention of Array.prototype.compare(). Lets make it more clever this time. It shall tell primitives from objects. One other thing is, arrays are ordered; so in my book [1,2] is not equal to [2,1]. Also this makes the job simpler.

Object.prototype.compare = function(o){
  var ok = Object.keys(this);
  return typeof o === "object" && ok.length === Object.keys(o).length ? ok.every(k => this[k] === o[k]) : false;
};
Array.prototype.compare = function(a){
  return this.every((e,i) => typeof a[i] === "object" ? a[i].compare(e) : a[i] === e);
}
var x = [{a:1, b:2}, {c:3, d:4}],
    y = [{b:2, a:1}, {d:4, c:3}],
    a = [1,2,3,4,5],
    b = [1,2,3,4,5],
    p = "fourtytwo",
    r = "thirtyseven",
    n = 42,
    m = 37;
document.writeln(x.compare(y)); // the question is answered here
document.writeln(a.compare(b));
document.writeln(p.compare(r)); // these primitives end up at Object prototype
document.writeln(n.compare(m)); // so modify Object.prototype.compare () accordingly
Toreador answered 11/5, 2016 at 21:28 Comment(8)
Your solution looks cool, thanks. However: new Object({a:1, b:2, c:3}).compare({c:3,b:2,a:1,d:0}); returns true, but should be false Same problems with deep object comparison. I'd prefer existing lodash solution.Arborescent
@Archer: hmm cool.. that's why it's v0.0.1. Seems we have to add a check for Object.keys().length stage too.Toreador
@Archer: did a quick fix. I guess next stage could be to make it dive deeper recursively, hand to hand with it's twin Array.prototype.compare()Toreador
good ;) But better to learn lodash. It's what it was designed for.Arborescent
@Arborescent Well may be... But I am a very library agnostic person. Think it this way, if you need a banana you wouldn't like a gorilla knocking at your door holding a banana with the whole jungle behind...Toreador
Sometime you need many different fruits and gorilla might get them to you... Better this way rather then plant all those fruits yourself.Arborescent
Nice effort but there's a god-like term for these cases: 'Don't reinvent the wheel'. Anyway, javascript es6 should natively include array and object comparisons with shallow and deep flags in my humble opinion.Rubber
How can I use your method in Angular with typescript?Scarcely
A
0

Combining answers from solution 1 & solution 2, with examples.

const isArrayEqual = (x, y) => (
_.size(x) === _.size(y) && _.isEmpty(_.xorWith(x, y, _.isEqual))
);

const result1 = isArrayEqual(
  [{a:1, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);

const result2 = isArrayEqual(
  [{a:1, b:2},{a:2, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);

const result3 = isArrayEqual(
  [{a:1, b:2},{a:1, b:2}],
  [{a:1, b:2}, {a:1, b:2}]
);

// if order is changed
const result4 = isArrayEqual(
  [{a:2, b:2},{a:1, b:2}],
  [{a:1, b:2}, {a:2, b:2}]
);

console.log('result1 should be false:', result1);
console.log('result2 should be false:', result2);
console.log('result3 should be true:', result3);
console.log('result4 should be true:', result4);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

In Js solutions, one line code :

import { size, isEmpty, isEqual, xorWith } from "lodash";    
export const isArrayOfObjectsEqual = (x, y) => size(x) === size(y) && isEmpty(xorWith(x, y, isEqual));
Assign answered 11/7, 2023 at 12:28 Comment(0)
W
0

_.isEqual (lodash) can compare array of objects.

//true
var result1 = _.isEqual(
  [{a:1, b:2}],
  [{a:1, b:2}]
);

//false
var result2 = _.isEqual(
  [{a:1, b:2}],
  [{a:1}]
);

//false
var result3 = _.isEqual(
  [{a:1, b:2}],
  []
);

document.write([
  '<div><label>result1: ', result1, '</label></div>',
  '<div><label>result2: ', result2, '</label></div>',
  '<div><label>result2: ', result3, '</label></div>',
].join(''));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js"></script>
Ward answered 11/7 at 10:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.