51

I am using _.isEqual that compares 2 array of objects (ex:10 properties each object), and it is working fine.

Now there are 2 properties (creation and deletion) that i need not to be a part of comparison.

Example:

var obj1 = {name: "James", age: 17, creation: "13-02-2016", deletion: "13-04-2016"}
var obj2 = {name: "Maria", age: 17, creation: "13-02-2016", deletion: "13-04-2016"}

// lodash method...
_.isEqual(firstArray, secondArray)
Rohit416
  • 3,416
  • 3
  • 24
  • 41
Marco Santos
  • 915
  • 2
  • 11
  • 24

6 Answers6

117

You can use omit() to remove specific properties in an object.

var result = _.isEqual(
  _.omit(obj1, ['creation', 'deletion']),
  _.omit(obj2, ['creation', 'deletion'])
);

var obj1 = {
  name: "James",
  age: 17,
  creation: "13-02-2016",
  deletion: "13-04-2016"
};

var obj2 = {
  name: "Maria",
  age: 17,
  creation: "13-02-2016",
  deletion: "13-04-2016"
};

var result = _.isEqual(
  _.omit(obj1, ['creation', 'deletion']),
  _.omit(obj2, ['creation', 'deletion'])
);

console.log(result);
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js"></script>
ryeballar
  • 29,658
  • 10
  • 65
  • 74
  • One last question, how would i do if instead being a object (ex: obj1) is a array of objects? – Marco Santos Jun 23 '16 at 11:32
  • I don't understand your question, state an example. – ryeballar Jun 23 '16 at 11:53
  • For example instead of having 1 object i have a array of objects where i wish to omit the properties creation and deletion, because i notice that omit function only support a object and not arrays of objects – Marco Santos Jun 23 '16 at 13:56
  • This will create a copy of each object. I recommend using `isEqualWith` instead. That should also work with any form of nested objects/arrays. – AJ Richardson May 16 '17 at 15:51
  • 1
    can you use this also to exclude all properties with a wildcard in their names like: `_.omit(obj, ['creat*'])` so it will exclude create, created, creation.. ? – Gobliins May 16 '18 at 08:02
  • You can consider using omitBy instead. It accepts a predicate function that determines whether a proprerty can be omitted or not. – ryeballar May 16 '18 at 11:43
  • Unless you have a really long list of things to compare, I would use `_.pick` instead of `omit`. It performs better and is more explicit. – theUtherSide Feb 06 '19 at 00:26
27

@ryeballar's answer is not great for large objects because you are creating a deep copy of each object every time you do the comparison.

It's better to use isEqualWith. For example, to ignore differences in the "creation" and "deletion" properties:

var result = _.isEqualWith(obj1, obj2, (value1, value2, key) => {
    return key === "creation" || key === "deletion" ? true : undefined;
});

EDIT (important caveat pointed out in the comments): if objects have different numbers of keys, then isEqualWith considers them to be different, regadless of what your customizer does. Therefore do not use this approach if you want to ignore an optional property. Instead, consider using _.isMatch(), _.isMatchWith(), or @ryeballar's _.omit() approach.

Note that if you're writing for ES5 and earlier, you'll have to replace the arrow syntax (() => {) with function syntax (function() {)

AJ Richardson
  • 6,610
  • 1
  • 49
  • 59
  • It seems `isEqualWith`doesn't work as expected ([see this issue](https://github.com/lodash/lodash/issues/2490)) – Bernardo Dal Corno Feb 19 '18 at 21:12
  • 1
    According to the lodash folks, that "issue" is expected behavior. It works fine, just keep in mind that for the first iteration, `value1` and `value2` will be your original objects, and `key` will be undefined. As long as you return `undefined` in this case (which my example does), then `isEqualWith` will recurse into the properties of the object. – AJ Richardson Nov 17 '18 at 13:36
  • 2
    On Lodash 4.17.11 it will work only if both objects have the same number of keys. Otherwise, `isEqualWith` will pre-check if both objects have the same number of keys and return `false` before getting to the next loop where it goes through each key. So if one object has the creation key and the other not it will actually not ignore that field. – maxcnunes Apr 10 '19 at 01:29
  • @maxcnunes how to workaround this inconvenience, without having to deep clone the objects as suggested in the previous answer? – Luke May 31 '19 at 06:58
  • I edited my answer - @Luke You could try `_.isMatch()` instead, since it will ignore all properties that are missing in the first object. – AJ Richardson Jun 01 '19 at 14:40
  • isEqualWith is also limited to 6 arguments in case you need to compare bigger objects – Marc Bollmann Oct 12 '22 at 09:00
6

_.omit creates deep copy of the object. If you need to exclude only root props it is better to create shallow copy using, for example, destructuring assignment:

const x = { a: 4, b: [1, 2], c: 'foo' }
const y = { a: 4, b: [1, 2], c: 'bar' }

const { c: xC, ...xWithoutC } = x
const { c: yC, ...yWithoutC } = y

_.isEqual(xWithoutC, yWithoutC) // true
xWithoutC.b === x.b             // true, would be false if you use _.omit

Best way is not to create copies at all (TypeScript):

function deepEqual(
  x?: object | null,
  y?: object | null,
  ignoreRootProps?: Set<string>
) {
  if (x == null || y == null) return x === y

  const keys = Object.keys(x)
  if (!_.isEqual(keys, Object.keys(y)) return false

  for (let key of keys) {
    if (ignoreRootProps && ignoreRootProps.has(key)) continue
    if (!_.isEqual(x[key], y[key])) return false
  }
  return true
}
Alexander Danilov
  • 3,038
  • 1
  • 30
  • 35
4

You could map your array into a "cleaned" array, then compare those.

// Create a function, to do some cleaning of the objects.
var clean = function(obj) {
    return {name: obj.name, age: obj.age};
};

// Create two new arrays, which are mapped, 'cleaned' copies of the original arrays.
var array1 = firstArray.map(clean);
var array2 = secondArray.map(clean);

// Compare the new arrays.
_.isEqual(array1, array2);

This has the downside that the clean function will need to be updated if the objects are expecting any new properties. It is possible to edit it so that it removes the two unwanted properties instead.

Hopeful Llama
  • 728
  • 5
  • 26
0

I see two options.

1) Make a second copy of each object that doesn't contain the creation or date.

2) Loop through all the properties and, assuming you know for certain that they both have the same properties, try something like this.

var x ={}
var y ={}
for (var property in x) {
if(property!="creation" || property!="deletion"){
if (x.hasOwnProperty(property)) {
        compare(x[property], y[property])
    }
}
}

Where compare() is some simple string or object comparison. If you are certain of the properties on one or both the objects, you can simplify this code a bit further, but this should work in most cases.

master565
  • 805
  • 1
  • 11
  • 27
0

My final solution required a full comparison ignoring an optional property so the above solutions did not work.

I used a shallow clone to remove the keys I wanted to ignore from each object before comparing with isEqual:

const equalIgnoring = (newItems, originalItems) => newItems.length === originalItems.length
    && newItems.every((newItem, index) => {
        const rest1 = { ...newItem };
        delete rest1.creation;
        delete rest1.deletion;
        const rest2 = { ...originalItems[index] };
        delete rest2.creation;
        delete rest2.deletion;
        return isEqual(rest1, rest2);
    });

If you want to check a subset for each item in the array this works:

const equalIgnoringExtraKeys = (fullObjs, partialObjs) => 
    fullObjs.length === partialObjs.length
    && fullObjs.every((fullObj, index) => isMatch(fullObj, partialObjs[index]));

If you also want to ignore a specific property and check subset:

const subsetIgnoringKeys = (fullObjs, partialObjs) => 
    fullObjs.length === partialObjs.length
    && fullObjs.every((fullObj, index) => isMatchWith(
        fullObj,
        partialObjs[index],
        (objValue, srcValue, key, object, source) => {
            if (["creation", "deletion"].includes(key)) {
                return true;
            }
            return undefined;
        }
    ));
Loren
  • 9,783
  • 4
  • 39
  • 49