How can I do a shallow comparison of the properties of two objects with Javascript or lodash?
Asked Answered
D

12

42

Is there a way I can do a shallow comparison that will not go down and compare the contents of objects inside of objects in Javascript or lodash? Note that I did check lodash, but it appears to perform a deep comparison which I don't want to do.

var a = { x: 1, y: 2}
var b = { x: 1, y: 3}

Is there some way for example to compare a and b?

Development answered 8/3, 2014 at 8:10 Comment(6)
Here the cases are different, right?Jac
Yes. I know this example does not include an object but if it did I want the contents of that object (present in both) to not be checked.Development
Its still not clear to me, but you can try JSON.stringify(a) === JSON.stringify(b)Jac
@thefourtheye, (1) that doesn't seem shallow, and (2) there is no guarantee about the order in JSON.stringify.Wealth
@PaulDraper I agree that, it might not be shallow but since the keys are the same, I don't think the order will matter much.Jac
I'm amazed that this isn't in Lodash.Spurious
W
32
function areEqualShallow(a, b) {
    for(var key in a) {
        if(!(key in b) || a[key] !== b[key]) {
            return false;
        }
    }
    for(var key in b) {
        if(!(key in a) || a[key] !== b[key]) {
            return false;
        }
    }
    return true;
}

Notes:

  • Since this is shallow, areEqualShallow({a:{}}, {a:{}}) is false.

  • areEqualShallow({a:undefined}, {}) is false.

  • This includes any properties from the prototype.

  • This uses === comparison. I assume that is what you want. NaN === NaN is one case that may yield unexpected results. If === is not what you want, substitute with the comparison you want.


EDIT: If the same keys are in each object, then

function areEqualShallow(a, b) {
    for(var key in a) {
        if(a[key] !== b[key]) {
            return false;
        }
    }
    return true;
}
Wealth answered 8/3, 2014 at 8:16 Comment(10)
Thanks. One thing I am sure of is that all the keys are there in each object. Only the values could be different. Could this be simplified to just one for loop ?Development
should check typeof? if a[key] and b[key] are child objects?Lemures
+1, though it should be noted that there are a few edge cases with ===. In particular, NaN is not equal to itself, and +0 and -0 are equal to each other (despite behaving differently in certain cases).Theorbo
@sabithpocker, why would you want to do that?Wealth
"This includes any properties from the prototype". Yes, probably should have a hasOwnProperty test in there.Gerrald
@RobG, unless the OP wants to include them in the comparison. The question doesn't specify. (I suspect the OP is thinking only about "plain" objects.)Wealth
@PaulDraper he doesnt want to compare objects inside objects and only non-objects in first level. In case a and b has a["f"] and b["f"] as {as : "asd"} then a[key] !== b[key] and returns false. I guess OP just want to compare non-objects.Lemures
@sabithpocker, a shallow comparison would have {f:{as:"asd"}} unequal from {f:{as:"asd"}}. A deep comparison would have them as equal.Wealth
the question is if OP wants { x: 1, y: 3, z : {as:"asd"}} equal to { x: 1, y: 3, z : {as:"asd"}} anyway OP got to tell thatLemures
@sabithpocker, your example highlights the differences between a "shallow" and a "deep" comparison. A shallow comparison has then as unequal; a deep comparison has them as equal.Wealth
C
79

Simple ES6 approach:

const shallowCompare = (obj1, obj2) =>
  Object.keys(obj1).length === Object.keys(obj2).length &&
  Object.keys(obj1).every(key => obj1[key] === obj2[key]);

Here I added the object keys amount equality checking for the following comparison should fail (an important case that usually does not taken into the account):

shallowCompare({ x: 1, y: 3}, { x: 1, y: 3, a: 1}); // false

2019 Update. Per Andrew Rasmussen' comment we also need to take into account undefined case. The problem with the previous approach is that the following comparison returns true:

({ foo: undefined })['foo'] === ({ bar: undefined })['foo'] // true

So, explicit keys existence check is needed. And it could be done with hasOwnProperty:

const shallowCompare = (obj1, obj2) =>
  Object.keys(obj1).length === Object.keys(obj2).length &&
  Object.keys(obj1).every(key => 
    obj2.hasOwnProperty(key) && obj1[key] === obj2[key]
  );
Cathrine answered 14/9, 2018 at 0:27 Comment(3)
This doesn't work for keys with undefined values. shallowCompare({ foo: undefined, }, { bar: undefined, }); // returns truePresidentship
@AndrewRasmussen I updated the answer and covered undefined situation. Thank you for your comment and failure code sample!Cathrine
Use Object.prototype.hasOwnProperty.call(obj2, key) instead to pass ESLint: eslint.org/docs/rules/no-prototype-builtinsWadesworth
W
32
function areEqualShallow(a, b) {
    for(var key in a) {
        if(!(key in b) || a[key] !== b[key]) {
            return false;
        }
    }
    for(var key in b) {
        if(!(key in a) || a[key] !== b[key]) {
            return false;
        }
    }
    return true;
}

Notes:

  • Since this is shallow, areEqualShallow({a:{}}, {a:{}}) is false.

  • areEqualShallow({a:undefined}, {}) is false.

  • This includes any properties from the prototype.

  • This uses === comparison. I assume that is what you want. NaN === NaN is one case that may yield unexpected results. If === is not what you want, substitute with the comparison you want.


EDIT: If the same keys are in each object, then

function areEqualShallow(a, b) {
    for(var key in a) {
        if(a[key] !== b[key]) {
            return false;
        }
    }
    return true;
}
Wealth answered 8/3, 2014 at 8:16 Comment(10)
Thanks. One thing I am sure of is that all the keys are there in each object. Only the values could be different. Could this be simplified to just one for loop ?Development
should check typeof? if a[key] and b[key] are child objects?Lemures
+1, though it should be noted that there are a few edge cases with ===. In particular, NaN is not equal to itself, and +0 and -0 are equal to each other (despite behaving differently in certain cases).Theorbo
@sabithpocker, why would you want to do that?Wealth
"This includes any properties from the prototype". Yes, probably should have a hasOwnProperty test in there.Gerrald
@RobG, unless the OP wants to include them in the comparison. The question doesn't specify. (I suspect the OP is thinking only about "plain" objects.)Wealth
@PaulDraper he doesnt want to compare objects inside objects and only non-objects in first level. In case a and b has a["f"] and b["f"] as {as : "asd"} then a[key] !== b[key] and returns false. I guess OP just want to compare non-objects.Lemures
@sabithpocker, a shallow comparison would have {f:{as:"asd"}} unequal from {f:{as:"asd"}}. A deep comparison would have them as equal.Wealth
the question is if OP wants { x: 1, y: 3, z : {as:"asd"}} equal to { x: 1, y: 3, z : {as:"asd"}} anyway OP got to tell thatLemures
@sabithpocker, your example highlights the differences between a "shallow" and a "deep" comparison. A shallow comparison has then as unequal; a deep comparison has them as equal.Wealth
U
6

Paul Draper's solution can be optimized by removing the compare in the second pass.

function areEqualShallow(a, b) {
  for (let key in a) {
    if (!(key in b) || a[key] !== b[key]) {
      return false;
    }
  }
  for (let key in b) {
    if (!(key in a)) {
      return false;
    }
  }
  return true;
}
Unpopular answered 19/8, 2016 at 14:22 Comment(0)
C
6

keeping in mind that it only for shallow and only for strings and numbers

function equals(obj1, obj2) {
  return Object.keys(obj1)
    .concat(Object.keys(obj2))
    .every(key => {
      return obj1[key] === obj2[key];
    });
}
Citarella answered 28/11, 2017 at 13:29 Comment(4)
It also checks for object references just fine which is what you should expect of a shallow comparison.Orizaba
This doesn't work for keys with undefined values. equals({ foo: undefined, }, { bar: undefined, }); // returns truePresidentship
@AndrewRasmussen technically those two are shallow equal because { foo: undefined } and { bar: undefined } is the same as describing {} just with extra stepsHomeopathy
I wouldn't call {} and { foo: undefined } shallow equal because they have different keys.Presidentship
C
5

This is lifted from fbjs:

/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @typechecks
 *
 */

/*eslint-disable no-self-compare */

'use strict';

var hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * inlined Object.is polyfill to avoid requiring consumers ship their own
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
function is(x, y) {
    // SameValue algorithm
    if (x === y) {
        // Steps 1-5, 7-10
        // Steps 6.b-6.e: +0 != -0
        return x !== 0 || 1 / x === 1 / y;
    } else {
        // Step 6.a: NaN == NaN
        return x !== x && y !== y;
    }
}

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA, objB) {
    if (is(objA, objB)) {
        return true;
    }

    if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
        return false;
    }

    var keysA = Object.keys(objA);
    var keysB = Object.keys(objB);

    if (keysA.length !== keysB.length) {
        return false;
    }

    // Test for A's keys different from B.
    for (var i = 0; i < keysA.length; i++) {
        if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
            return false;
        }
    }

    return true;
}

module.exports = shallowEqual;

I recommend copying it into your own project if you need to use it, as their README clearly states that they may remove or modify this and any other code in the lib without warning.

Chifley answered 5/6, 2016 at 1:57 Comment(1)
here's a direct link to the codeAppendicular
G
1

To do a "shallow" comparison where inherited properties should be ignored and NaN should equal NaN, the following should do the job. It checks that each object has the same own properties and that the values are === or both NaN:

function checkProperties(a, b) {
    var equal = true;

    // For each property of a
    for (var p in a) {

        // Check that it's an own property
        if (a.hasOwnProperty(p)) {

            // Check that b has a same named own property and that the values
            // are === or both are NaN
            if (!b.hasOwnProperty(p) || 
               (b[p] !== a[p] && !(typeof b[p] == 'number' && typeof a[p] == 'number' && isNaN(b[p] && isNaN(a[p]))))) {

                // If not, set equal to false
                equal = false;
            }
        }

        // If equal is false, stop processing properties
        if (!equal) break;
    }
    return equal;
}

Using recent features like Object.keys to get own properties, then

function checkProperties(a, b) {
  return Object.keys(a).every(function(p) {
    return b.hasOwnProperty(p) && 
           (b[p] == a[p] || (typeof a[p] == 'number' && typeof b[p] == 'number' && isNaN(b[p]) && isNaN(a[p])));
   });
}

// Compare a to b and b to a
function areEqualShallow(a, b) {
  return checkProperties(a, b) && checkProperties(b, a);
}

// Minimal testing
var a = {foo:'a', bar:2};
var b = {foo:'a', bar:2};
var c = {foo:'c', bar:2};
var d = {foo:'a', bar:2, fum:0};

console.log('a equal to b? ' + areEqualShallow(a,b)); // true
console.log('a equal to c? ' + areEqualShallow(a,c)); // false
console.log('a equal to d? ' + areEqualShallow(a,d)); // false

With newer features, the checkProperties function can be simplified somewhat:

Gerrald answered 8/3, 2014 at 10:10 Comment(4)
I think that returning false immediately is cleaner than having a flag and breaking from loops manuallySillimanite
Yes, you're correct. Also, there should be a typeof test before isNaN since it should only be used on type number, isNaN(someNonNumber) always returns true so mismatched values slip through. ;-)Gerrald
Also, you should always use === instead of ==. Double equals are error-proneSillimanite
@George—I disagree, see Which equals operator (== vs ===) should be used in JavaScript comparisons?Gerrald
O
1
const shallowEq = (a, b) =>
  [...Object.keys(a), ...Object.keys(b)].every((k) => b[k] === a[k]);

If you really need to check undefined values, then this extension should satisfy @AndrewRasmussen:

const shallowEq2 = (a, b) =>
  [...Object.keys(a), ...Object.keys(b)].every(k => b[k] === a[k] && a.hasOwnProperty(k) && b.hasOwnProperty(k)); 

In most use cases you don't really need all the checks, and you only want to see if b contains everything a contains. Then an a-centric check would be really really terse:

const shallowEq3 = (a, b) => Object.keys(a).every(k => b[k] === a[k]);
Olympian answered 2/8, 2020 at 3:6 Comment(1)
Don't miss the statement that shallowEq3 is for when "you only want to see if b contains everything a contains" . It will return true for a = { x: 1 }, b = { x: 1, y: 2 }.Acidify
A
0
var a = { x: 1, y: 2}
var b = { x: 1, y: 3}

function shalComp (obj1, obj2) {
 var verdict = true;
 for (var key in obj1) {
  if (obj2[key] != obj1[key]) {
   verdict = false;
  }
 }
 return verdict;
}
Arguello answered 23/9, 2017 at 5:19 Comment(0)
S
0

const isEqual = (a, b) => {
  // compare keys
  const xKeys = Object.keys(a);
  const bKeys = Object.keys(b);

  if (xKeys.length !== bKeys.length) {
    return false;
  }

  // compare values
  for (let objKeys in xKeys) {
    if (xKeys[objKeys !== bKeys[objKeys]]) {
      return false;
    }
  }
  return true;
};

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

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

console.log(isEqual(a, b)); // true

And you can see this video is very helpful for your question : JS Tutorial: Find if Two Object Values are Equal to Each Other

Sancha answered 9/3, 2021 at 7:10 Comment(0)
S
0

Here is a straightforward approach for ES6:

function shallowEquals(a, b) {
    if (a === b)
        return true;
    if (a === null || b === null)
        return false;

    const ka = Object.keys(a);
    const kb = Object.keys(b);
    if (ka.length !== kb.length)
        return false;

    return ka.every(ki => a[ki] === b[ki]);
}

This code assumes that a and b are both objects. They may be null, but not undefined.

This function implements a variety of short-circuits to improve performance in common cases. Per the question, this is a shallow equals method, so it will not work properly if any values are not ===-comparable.

If your objects might contain undefined-valued properties, then you'll also need to check that the keys themselves are the same, per the below. This is uncommon, but worth mentioning.

function shallowEqualsWithUndefinedValuedProperties(a, b) {
    if (a === b)
        return true;
    if (a === null || b === null)
        return false;

    const ka = Object.keys(a);
    const kb = Object.keys(b);
    if (ka.length !== kb.length)
        return false;

    // You only need this check if your objects may contain
    // undefined-valued properties, e.g., {"a":undefined}
    // If your objects have many properties, then it might
    // improve performance to create a Set from the contents
    // of kb and test against that instead.
    if (ka.some(kai => kb.indexOf(kai) === -1))
        return false;

    return ka.every(ki => a[ki] === b[ki]);
}
Sideman answered 27/5, 2023 at 1:5 Comment(0)
C
0

I just needed something to quickly compare prevProps and this.props

    const shallowDiff = (a, b) => {
      return Object.keys(a).reduce((c, k) => {
        if (a[k] != b[k]) return {
          ...c,
          [k]: a[k]+'::'+b[k],
        };
        return c;
      }, {});
    };
Champollion answered 21/7, 2023 at 0:14 Comment(0)
W
0

I discovered a library on Github designed for this purpose: https://github.com/dashed/shallowequal . It has over a 170 stars and has test code.

Just in the process of merging into my toolchain for full testing.

Wag answered 12/11, 2023 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.