486

I have 2 nested objects which are different and I need to know if they have a difference in one of their nested properties.

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

The object could be much more complex with more nested properties. But this one is a good example. I have the option to use recursive functions or something with lodash...

ggorlen
  • 44,755
  • 7
  • 76
  • 106
JLavoie
  • 16,678
  • 8
  • 33
  • 39
  • For deep comparison https://stackoverflow.com/a/46003894/696535 – Pawel Sep 01 '17 at 15:53
  • 19
    `_.isEqual(value, other)` Performs a deep comparison between two values to determine if they are equivalent. https://lodash.com/docs#isEqual – Lukas Liesis Sep 04 '18 at 11:17
  • JSON.stringify() – xgqfrms Oct 09 '19 at 02:41
  • 47
    JSON.stringify() is wrong: JSON.stringify({a:1,b:2}) !== JSON.stringify({b:2,a:1}) – Shl Dec 10 '19 at 12:22
  • note that if any of the values are arrays, they must be sorted first (can use `_.sortBy` on the individual prop - yes, it will make it somewhat more complicated), otherwise a simple `_.isEqual` comparision will fail... – benomatis Feb 07 '22 at 19:07
  • @benomatis I don't think `[1, 2]` necessarily should equal `[2, 1]`. In some situations, sure, but I wouldn't say it's a universal rule. – aioobe Aug 31 '22 at 20:45
  • @aioobe right, depends on if the OP wants to compare the contents only, or if order matters to them too. – benomatis Sep 01 '22 at 13:55

25 Answers25

691

An easy and elegant solution is to use _.isEqual, which performs a deep comparison:

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

console.log(_.isEqual(a, b)); // returns false if different
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

However, this solution doesn't show which property is different.

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
JLavoie
  • 16,678
  • 8
  • 33
  • 39
  • 4
    I know that the answer is pretty old, but I want to add, that `_.isEqual` can be pretty tricky. If you copy the object and change some values in there it will still show true, because the reference is the same. So one should be careful using this function. – oruckdeschel Sep 13 '18 at 13:51
  • 54
    @oruckdeschel if the reference is the same, it is the same object. hence it is equal. this is a pointer being tricky not lodash. lodash is awesome. – guy mograbi Sep 25 '18 at 02:09
  • The method should then be called hasSameReference not isEqual. – Walter Monecke Feb 22 '22 at 12:32
  • 3
    @WalterMonecke no, it shouldn't. `hasSameReference` is just plain `===`, which is not what this does. `_.isEqual` returns true for same references because that's a non-strict subset of objects equal in values and properties, but it also returns whether two _different objects that aren't references_ are (deeply) equal in values and properties. – ggorlen Jul 26 '22 at 19:09
324

If you need to know which properties are different, use reduce():

_.reduce(a, function(result, value, key) {
    return _.isEqual(value, b[key]) ?
        result : result.concat(key);
}, []);
// → [ "prop2" ]
Adam Boduch
  • 11,023
  • 3
  • 30
  • 38
  • 49
    Note that this will only output the first level different properties. (So it's not really *deep* in the sense of outputting the properties which are different.) – Bloke May 05 '16 at 14:03
  • 27
    Also, this will not pick up properties in b that are not in a. – Ed Staub Aug 23 '16 at 23:47
  • 7
    and `_.reduce(a, (result, value, key) => _.isEqual(value, b[key]) ? result : result.concat(key), [])` for a one line ES6 solution – Dotgreg Nov 04 '17 at 09:36
  • 2
    A version concating the key:value `let edited = _.reduce(a, function(result, value, key) { return _.isEqual(value, b[key]) ? result : result.concat( { [key]: value } ); }, []);` – Aline Matos Sep 23 '18 at 14:31
76

For anyone stumbling upon this thread, here's a more complete solution. It will compare two objects and give you the key of all properties that are either only in object1, only in object2, or are both in object1 and object2 but have different values:

/*
 * Compare two objects by reducing an array of keys in obj1, having the
 * keys in obj2 as the intial value of the result. Key points:
 *
 * - All keys of obj2 are initially in the result.
 *
 * - If the loop finds a key (from obj1, remember) not in obj2, it adds
 *   it to the result.
 *
 * - If the loop finds a key that are both in obj1 and obj2, it compares
 *   the value. If it's the same value, the key is removed from the result.
 */
function getObjectDiff(obj1, obj2) {
    const diff = Object.keys(obj1).reduce((result, key) => {
        if (!obj2.hasOwnProperty(key)) {
            result.push(key);
        } else if (_.isEqual(obj1[key], obj2[key])) {
            const resultKeyIndex = result.indexOf(key);
            result.splice(resultKeyIndex, 1);
        }
        return result;
    }, Object.keys(obj2));

    return diff;
}

Here's an example output:

// Test
let obj1 = {
    a: 1,
    b: 2,
    c: { foo: 1, bar: 2},
    d: { baz: 1, bat: 2 }
}

let obj2 = {
    b: 2, 
    c: { foo: 1, bar: 'monkey'}, 
    d: { baz: 1, bat: 2 }
    e: 1
}
getObjectDiff(obj1, obj2)
// ["c", "e", "a"]

If you don't care about nested objects and want to skip lodash, you can substitute the _.isEqual for a normal value comparison, e.g. obj1[key] === obj2[key].

Johan Persson
  • 1,875
  • 11
  • 11
  • 1
    This chosen answer is correct for just testing equality. If you need to know what the differences are, there's no obvious way to list them, but this answer is pretty good, just giving a list of top-level property keys where there's a difference. (And it gives the answer as a function, which makes it usable.) – Sigfried Dec 12 '16 at 13:47
  • What is the difference between doing this and just using _.isEqual(obj1, obj2) ? What does adding the check for hasOwnProperty do that _.isEqual does not? I was under the assumption that if obj1 had a property that obj2 did not have, _.isEqual would not return true.. ? – JakeD Aug 04 '17 at 16:44
  • 3
    @Jaked222 - the difference is that isEqual returns a boolean telling you if the objects are equal or not, while the function above tells you _what_ is different between the two objects (if they are different). If you're only interested in knowing if two objects are the same, isEqual is quite enough. In many cases though, you want to know what the difference is betwen two objects. An example could be if you want to detect changes before and after something and then dispatch an event based on the changes. – Johan Persson Aug 05 '17 at 17:28
47

Based on the answer by Adam Boduch, I wrote this function which compares two objects in the deepest possible sense, returning paths that have different values as well as paths missing from one or the other object.

The code was not written with efficiency in mind, and improvements in that regard are most welcome, but here is the basic form:

var compare = function (a, b) {

  var result = {
    different: [],
    missing_from_first: [],
    missing_from_second: []
  };

  _.reduce(a, function (result, value, key) {
    if (b.hasOwnProperty(key)) {
      if (_.isEqual(value, b[key])) {
        return result;
      } else {
        if (typeof (a[key]) != typeof ({}) || typeof (b[key]) != typeof ({})) {
          //dead end.
          result.different.push(key);
          return result;
        } else {
          var deeper = compare(a[key], b[key]);
          result.different = result.different.concat(_.map(deeper.different, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_second = result.missing_from_second.concat(_.map(deeper.missing_from_second, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_first = result.missing_from_first.concat(_.map(deeper.missing_from_first, (sub_path) => {
            return key + "." + sub_path;
          }));
          return result;
        }
      }
    } else {
      result.missing_from_second.push(key);
      return result;
    }
  }, result);

  _.reduce(b, function (result, value, key) {
    if (a.hasOwnProperty(key)) {
      return result;
    } else {
      result.missing_from_first.push(key);
      return result;
    }
  }, result);

  return result;
}

You can try the code using this snippet (running in full page mode is recommended):

var compare = function (a, b) {

  var result = {
    different: [],
    missing_from_first: [],
    missing_from_second: []
  };

  _.reduce(a, function (result, value, key) {
    if (b.hasOwnProperty(key)) {
      if (_.isEqual(value, b[key])) {
        return result;
      } else {
        if (typeof (a[key]) != typeof ({}) || typeof (b[key]) != typeof ({})) {
          //dead end.
          result.different.push(key);
          return result;
        } else {
          var deeper = compare(a[key], b[key]);
          result.different = result.different.concat(_.map(deeper.different, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_second = result.missing_from_second.concat(_.map(deeper.missing_from_second, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_first = result.missing_from_first.concat(_.map(deeper.missing_from_first, (sub_path) => {
            return key + "." + sub_path;
          }));
          return result;
        }
      }
    } else {
      result.missing_from_second.push(key);
      return result;
    }
  }, result);

  _.reduce(b, function (result, value, key) {
    if (a.hasOwnProperty(key)) {
      return result;
    } else {
      result.missing_from_first.push(key);
      return result;
    }
  }, result);

  return result;
}

var a_editor = new JSONEditor($('#a')[0], {
  name: 'a',
  mode: 'code'
});
var b_editor = new JSONEditor($('#b')[0], {
  name: 'b',
  mode: 'code'
});

var a = {
  same: 1,
  different: 2,
  missing_from_b: 3,
  missing_nested_from_b: {
    x: 1,
    y: 2
  },
  nested: {
    same: 1,
    different: 2,
    missing_from_b: 3
  }
}

var b = {
  same: 1,
  different: 99,
  missing_from_a: 3,
  missing_nested_from_a: {
    x: 1,
    y: 2
  },
  nested: {
    same: 1,
    different: 99,
    missing_from_a: 3
  }
}

a_editor.set(a);
b_editor.set(b);

var result_editor = new JSONEditor($('#result')[0], {
  name: 'result',
  mode: 'view'
});

var do_compare = function() {
  var a = a_editor.get();
  var b = b_editor.get();
  result_editor.set(compare(a, b));
}
#objects {} #objects section {
  margin-bottom: 10px;
}
#objects section h1 {
  background: #444;
  color: white;
  font-family: monospace;
  display: inline-block;
  margin: 0;
  padding: 5px;
}
.jsoneditor-outer, .ace_editor {
min-height: 230px !important;
}
button:hover {
  background: orangered;
}
button {
  cursor: pointer;
  background: red;
  color: white;
  text-align: left;
  font-weight: bold;
  border: 5px solid crimson;
  outline: 0;
  padding: 10px;
  margin: 10px 0px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.5.10/jsoneditor.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.5.10/jsoneditor.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="objects">
  <section>
    <h1>a (first object)</h1>
    <div id="a"></div>
  </section>
  <section>
    <h1>b (second object)</h1>
    <div id="b"></div>
  </section>
  <button onClick="do_compare()">compare</button>
  <section>
    <h1>result</h1>
    <div id="result"></div>
  </section>
</div>
Community
  • 1
  • 1
Rashad Saleh
  • 2,686
  • 1
  • 23
  • 28
  • 7
    I just fixed the bug, but to let you know, you should check key existence within an object `b` [using `b.hasOwnProperty(key)` or `key in b`](http://stackoverflow.com/q/1098040/578288), not with `b[key] != undefined`. With the old version that used `b[key] != undefined`, the function returned an incorrect diff for objects containing `undefined`, as in `compare({disabled: undefined}, {disabled: undefined})`. In fact, the old version also had problems with `null`; you can avoid problems like that by always using [`===` and `!==`](http://stackoverflow.com/a/359509/578288) instead of `==` and `!=`. – Rory O'Kane Mar 23 '17 at 21:28
35

Here's a concise solution using Lodash:

_.differenceWith(a, b, _.isEqual);

Note both inputs need to be arrays (possibly an array of one object).

Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
DPG
  • 685
  • 5
  • 5
  • 21
    Doesn't seem to work with objects for me. Instead returns an empty array. – tomhughes Nov 22 '17 at 11:24
  • 2
    Also getting empty array with Lodash 4.17.4 – aristidesfl Jan 25 '18 at 08:59
  • @Z.Khullah If it did work in this way, it is not documented. – Brendon Jan 29 '18 at 17:05
  • 2
    @Brendon , @THughes, @aristidesfl sorry, I've mixed things, it works with arrays of objects, but not for deep object comparisons. As it turns out, if neither parameters are arrays, lodash will just return `[]`. – Bernardo Dal Corno Jan 29 '18 at 17:36
  • 2
    looks like it works only with arrays. But no problem to put an object into an array. `_.differenceWith([object1], [object2], _.isEqual);` if the returned array is empty - it's mean that - there is no difference if the array is not empty - there is difference – shutsman Sep 14 '20 at 08:58
  • 1
    `_.differenceWith([object1], [object2], _.isEqual);` is equivalent to `_.isEqual(object1, object2)` so it's useless to use `differenceWith ` – jaumard Sep 20 '21 at 09:59
15

To recursively show how an object is different with other you can use _.reduce combined with _.isEqual and _.isPlainObject. In this case you can compare how a is different with b or how b is different with a:

const objectA = {
  a: {
    1: "SAME WILL BE MISSING IN RESULT", 
    2: "BBB", 
    3: [1, 2, 3]
  }, 
  b: "not", 
  c: "foo bar"
};
const objectB = {
  a: {
    1: "SAME WILL BE MISSING IN RESULT",
    2: [1, 2]
  }, 
  b: "foo", 
  c: "bar"
};

const diff = function(obj1, obj2) {
  return _.reduce(obj1, function(result, value, key) {
if (_.isPlainObject(value)) {
  result[key] = diff(value, obj2[key]);
} else if (!_.isEqual(value, obj2[key])) {
  result[key] = value;
}
return result;
  }, {});
};

const diffAOverB = diff(objectA, objectB);
const diffBOverA = diff(objectA, objectB);
console.log(diffAOverB);
console.log(diffBOverA);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>
zero_cool
  • 3,960
  • 5
  • 39
  • 54
Santiago Bendavid
  • 961
  • 1
  • 9
  • 16
10

This code returns an object with all properties that have a different value and also values of both objects. Useful to logging the difference.

var allkeys = _.union(_.keys(obj1), _.keys(obj2));
var difference = _.reduce(allkeys, function (result, key) {
  if ( !_.isEqual(obj1[key], obj2[key]) ) {
    result[key] = {obj1: obj1[key], obj2: obj2[key]}
  }
  return result;
}, {});
skfd
  • 2,528
  • 1
  • 19
  • 29
blikblum
  • 308
  • 2
  • 6
8

Simple use _.isEqual method, it will work for all comparing...

  • Note: This method supports comparing arrays, array buffers, booleans, * date objects, error objects, maps, numbers, Object objects, regexes, * sets, strings, symbols, and typed arrays. Object objects are compared * by their own, not inherited, enumerable properties. Functions and DOM * nodes are not supported.

So if you have below:

 const firstName = {name: "Alireza"};
 const otherName = {name: "Alireza"};

If you do: _.isEqual(firstName, otherName);,

it will return true

And if const fullName = {firstName: "Alireza", familyName: "Dezfoolian"};

If you do: _.isEqual(firstName, fullName);,

will return false

Alireza
  • 100,211
  • 27
  • 269
  • 172
6

There have been many answers posted but for those curious to avoid writing any code to calculate difference between two objects having any type of structure there is actually a library to do this. Lodash isEqual only returns true or false it doesn't return any information about the changed properties. https://www.npmjs.com/package/deep-diff

its returns full detail of differences between two objects

import DeepDiff from 'deep-diff';
let a = {...} //some object
let b = {...} //some object 
var differences = DeepDiff.diff(a, b);

Similar question has also been asked in this thread Getting the difference between 2 JSON objects

Nabeel Hussain Shah
  • 774
  • 2
  • 9
  • 15
5

As it was asked, here's a recursive object comparison function. And a bit more. Assuming that primary use of such function is object inspection, I have something to say. Complete deep comparison is a bad idea when some differences are irrelevant. For example, blind deep comparison in TDD assertions makes tests unnecessary brittle. For that reason, I'd like to introduce a much more valuable partial diff. It is a recursive analogue of a previous contribution to this thread. It ignores keys not present in a

var bdiff = (a, b) =>
    _.reduce(a, (res, val, key) =>
        res.concat((_.isPlainObject(val) || _.isArray(val)) && b
            ? bdiff(val, b[key]).map(x => key + '.' + x) 
            : (!b || val != b[key] ? [key] : [])),
        []);

BDiff allows checking for expected values while tolerating other properties, which is exactly what you want for automatic inspection. This allows building all kinds of advanced assertions. For example:

var diff = bdiff(expected, actual);
// all expected properties match
console.assert(diff.length == 0, "Objects differ", diff, expected, actual);
// controlled inequality
console.assert(diff.length < 3, "Too many differences", diff, expected, actual);

Returning to the complete solution. Building a full traditional diff with bdiff is trivial:

function diff(a, b) {
    var u = bdiff(a, b), v = bdiff(b, a);
    return u.filter(x=>!v.includes(x)).map(x=>' < ' + x)
    .concat(u.filter(x=>v.includes(x)).map(x=>' | ' + x))
    .concat(v.filter(x=>!u.includes(x)).map(x=>' > ' + x));
};

Running above function on two complex objects will output something similar to this:

 [
  " < components.0.components.1.components.1.isNew",
  " < components.0.cryptoKey",
  " | components.0.components.2.components.2.components.2.FFT.min",
  " | components.0.components.2.components.2.components.2.FFT.max",
  " > components.0.components.1.components.1.merkleTree",
  " > components.0.components.2.components.2.components.2.merkleTree",
  " > components.0.components.3.FFTResult"
 ]

Finally, in order to have a glimpse into how the values differ, we may want to directly eval() the diff output. For that, we need an uglier version of bdiff that outputs syntactically correct paths:

// provides syntactically correct output
var bdiff = (a, b) =>
    _.reduce(a, (res, val, key) =>
        res.concat((_.isPlainObject(val) || _.isArray(val)) && b
            ? bdiff(val, b[key]).map(x => 
                key + (key.trim ? '':']') + (x.search(/^\d/)? '.':'[') + x)
            : (!b || val != b[key] ? [key + (key.trim ? '':']')] : [])),
        []);

// now we can eval output of the diff fuction that we left unchanged
diff(a, b).filter(x=>x[1] == '|').map(x=>[x].concat([a, b].map(y=>((z) =>eval('z.' + x.substr(3))).call(this, y)))));

That will output something similar to this:

[" | components[0].components[2].components[2].components[2].FFT.min", 0, 3]
[" | components[0].components[2].components[2].components[2].FFT.max", 100, 50]

MIT license ;)

user46748
  • 145
  • 2
  • 4
4

I took a stab a Adam Boduch's code to output a deep diff - this is entirely untested but the pieces are there:

function diff (obj1, obj2, path) {
    obj1 = obj1 || {};
    obj2 = obj2 || {};

    return _.reduce(obj1, function(result, value, key) {
        var p = path ? path + '.' + key : key;
        if (_.isObject(value)) {
            var d = diff(value, obj2[key], p);
            return d.length ? result.concat(d) : result;
        }
        return _.isEqual(value, obj2[key]) ? result : result.concat(p);
    }, []);
}

diff({ foo: 'lol', bar: { baz: true }}, {}) // returns ["foo", "bar.baz"]
Deminetix
  • 2,866
  • 26
  • 21
  • 1
    Works like a charm, just that the order of obj1 and obj2 is important. For example: `diff({}, { foo: 'lol', bar: { baz: true }}) // returns []` – amangpt777 Mar 31 '17 at 11:04
4

Here is a simple Typescript with Lodash deep difference checker which will produce a new object with just the differences between an old object and a new object.

For example, if we had:

const oldData = {a: 1, b: 2};
const newData = {a: 1, b: 3};

the resulting object would be:

const result: {b: 3};

It is also compatible with multi-level deep objects, for arrays it may need some tweaking.

import * as _ from "lodash";

export const objectDeepDiff = (data: object | any, oldData: object | any) => {
  const record: any = {};
  Object.keys(data).forEach((key: string) => {
    // Checks that isn't an object and isn't equal
    if (!(typeof data[key] === "object" && _.isEqual(data[key], oldData[key]))) {
      record[key] = data[key];
    }
    // If is an object, and the object isn't equal
    if ((typeof data[key] === "object" && !_.isEqual(data[key], oldData[key]))) {
      record[key] = objectDeepDiff(data[key], oldData[key]);
    }
  });
  return record;
};
jmoore255
  • 321
  • 4
  • 15
  • The question isn't tagged Typescript, but it's still a nice answer – Lee Goddard May 31 '21 at 13:48
  • I really like your implementation, but there's a problem with it. In the first if, instead of `if (!(typeof data[key] === "object" && _.isEqual(data[key], oldData[key])))`, it should be `if (typeof data[key] !== 'object' && !_.isEqual(data[key], oldData[key]))` – Otavio Bonder Feb 04 '22 at 20:37
4

I need to know if they have difference in one of their nested properties

Other answers provide potentially satisfactory solutions to this problem, but it is sufficiently difficult and common that it looks like there's a very popular package to help solve this issue deep-object-diff.

To use this package you'd need to npm i deep-object-diff then:

const { diff } = require('deep-object-diff');
var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

if (!_.isEqual(a, b)) {
  const abDiff = diff(a, b);
  console.log(abDiff);
  /*
  {
    prop2: {
      prop3: 3
    }
  }
  */
}

// or alternatively
const abDiff = diff(a, b);
if(!_.isEmpty(abDiff)) {
  // if a diff exists then they aren't deeply equal
  // perform needed actions with diff...
}

Here's a more detailed case with property deletions directly from their docs:

const lhs = {
  foo: {
    bar: {
      a: ['a', 'b'],
      b: 2,
      c: ['x', 'y'],
      e: 100 // deleted
    }
  },
  buzz: 'world'
};

const rhs = {
  foo: {
    bar: {
      a: ['a'], // index 1 ('b')  deleted
      b: 2, // unchanged
      c: ['x', 'y', 'z'], // 'z' added
      d: 'Hello, world!' // added
    }
  },
  buzz: 'fizz' // updated
};

console.log(diff(lhs, rhs)); // =>
/*
{
  foo: {
    bar: {
      a: {
        '1': undefined
      },
      c: {
        '2': 'z'
      },
      d: 'Hello, world!',
      e: undefined
    }
  },
  buzz: 'fizz'
}
*/

For implementation details and other usage info, refer to that repo.

dracstaxi
  • 2,417
  • 1
  • 13
  • 12
3

Deep compare using a template of (nested) properties to check

function objetcsDeepEqualByTemplate(objectA, objectB, comparisonTemplate) {
  if (!objectA || !objectB) return false

  let areDifferent = false
  Object.keys(comparisonTemplate).some((key) => {
    if (typeof comparisonTemplate[key] === 'object') {
      areDifferent = !objetcsDeepEqualByTemplate(objectA[key], objectB[key], comparisonTemplate[key])
      return areDifferent
    } else if (comparisonTemplate[key] === true) {
      areDifferent = objectA[key] !== objectB[key]
      return areDifferent
    } else {
      return false
    }
  })

  return !areDifferent
}

const objA = { 
  a: 1,
  b: {
    a: 21,
    b: 22,
  },
  c: 3,
}

const objB = { 
  a: 1,
  b: {
    a: 21,
    b: 25,
  },
  c: true,
}

// template tells which props to compare
const comparisonTemplateA = {
  a: true,
  b: {
    a: true
  }
}
objetcsDeepEqualByTemplate(objA, objB, comparisonTemplateA)
// returns true

const comparisonTemplateB = {
  a: true,
  c: true
}
// returns false
objetcsDeepEqualByTemplate(objA, objB, comparisonTemplateB)

This will work in the console. Array support could be added if needed

Pawel
  • 16,093
  • 5
  • 70
  • 73
3

I know this doesn't directly answer the OP's question but I was led here by searching for how to remove lodash. So hopefully this helps someone else in a similar position as me.

Credit goes to @JohanPersson. I built off of that answer to implement comparing deeply nested values and getting the key reference to the diffs

getObjectDiff = (obj1, obj2) => {
    const obj1Props = Object.keys(obj1);
    const obj2Props = Object.keys(obj2);

    const keysWithDiffValue = obj1Props.reduce((keysWithDiffValueAccumulator, key) => {
      const propExistsOnObj2 = obj2.hasOwnProperty(key);
      const hasNestedValue = obj1[key] instanceof Object && obj2[key] instanceof Object;
      const keyValuePairBetweenBothObjectsIsEqual = obj1[key] === obj2[key];

      if (!propExistsOnObj2) {
        keysWithDiffValueAccumulator.push(key);
      } else if (hasNestedValue) {
        const keyIndex = keysWithDiffValueAccumulator.indexOf(key);
        if (keyIndex >= 0) {
          keysWithDiffValueAccumulator.splice(keyIndex, 1);
        }
        const nestedDiffs = getObjectDiff(obj1[key], obj2[key]);
        for (let diff of nestedDiffs) {
          keysWithDiffValueAccumulator.push(`${key}.${diff}`);
        }
      } else if (keyValuePairBetweenBothObjectsIsEqual) {
        const equalValueKeyIndex = keysWithDiffValueAccumulator.indexOf(key);
        keysWithDiffValueAccumulator.splice(equalValueKeyIndex, 1);
      }
      return keysWithDiffValueAccumulator;
    }, obj2Props);

    return keysWithDiffValue;
  }
const obj1 = {a0: {a1: {a2: {a3: 'Im here'}}}};
const obj2 = {a0: {a1: {a2: {a3: 'Not here', b3: 'some'}}}};
console.log('final', getObjectDiff(obj1, obj2));
2

Without use of lodash/underscore, I have written this code and is working fine for me for a deep comparison of object1 with object2

function getObjectDiff(a, b) {
    var diffObj = {};
    if (Array.isArray(a)) {
        a.forEach(function(elem, index) {
            if (!Array.isArray(diffObj)) {
                diffObj = [];
            }
            diffObj[index] = getObjectDiff(elem, (b || [])[index]);
        });
    } else if (a != null && typeof a == 'object') {
        Object.keys(a).forEach(function(key) {
            if (Array.isArray(a[key])) {
                var arr = getObjectDiff(a[key], b[key]);
                if (!Array.isArray(arr)) {
                    arr = [];
                }
                arr.forEach(function(elem, index) {
                    if (!Array.isArray(diffObj[key])) {
                        diffObj[key] = [];
                    }
                    diffObj[key][index] = elem;
                });
            } else if (typeof a[key] == 'object') {
                diffObj[key] = getObjectDiff(a[key], b[key]);
            } else if (a[key] != (b || {})[key]) {
                diffObj[key] = a[key];
            } else if (a[key] == (b || {})[key]) {
                delete a[key];
            }
        });
    }
    Object.keys(diffObj).forEach(function(key) {
        if (typeof diffObj[key] == 'object' && JSON.stringify(diffObj[key]) == '{}') {
            delete diffObj[key];
        }
    });
    return diffObj;
}
Sridhar Gudimela
  • 564
  • 8
  • 14
2

This is my solution to the problem

const _ = require('lodash');

var objects = [{ 'x': 1, 'y': 2, 'z':3, a:{b:1, c:2, d:{n:0}}, p:[1, 2, 3]  }, { 'x': 2, 'y': 1, z:3, a:{b:2, c:2,d:{n:1}}, p:[1,3], m:3  }];

const diffFn=(a,b, path='')=>_.reduce(a, function(result, value, key) {

    if(_.isObjectLike(value)){
      if(_.isEqual(value, b[key])){
        return result;
      }else{

return result.concat(diffFn(value, b[key], path?(`${path}.${key}`):key))
      }
    }else{
return _.isEqual(value, b[key]) ?
        result : result.concat(path?(`${path}.${key}`):key);
    }
    
}, []);

const diffKeys1=diffFn(objects[0], objects[1])
const diffKeys2=diffFn(objects[1], objects[0])
const diffKeys=_.union(diffKeys1, diffKeys2)
const res={};

_.forEach(diffKeys, (key)=>_.assign(res, {[key]:{ old: _.get(objects[0], key), new:_.get(objects[1], key)} }))

res
/*
Returns
{
  x: { old: 1, new: 2 },
  y: { old: 2, new: 1 },
  'a.b': { old: 1, new: 2 },
  'a.d.n': { old: 0, new: 1 },
  'p.1': { old: 2, new: 3 },
  'p.2': { old: 3, new: undefined },
  m: { old: undefined, new: 3 }
}
*/
1

Completing the answer from Adam Boduch, this one takes into differences in properties

const differenceOfKeys = (...objects) =>
  _.difference(...objects.map(obj => Object.keys(obj)));
const differenceObj = (a, b) => 
  _.reduce(a, (result, value, key) => (
    _.isEqual(value, b[key]) ? result : [...result, key]
  ), differenceOfKeys(b, a));
Ricardo Freitas
  • 533
  • 1
  • 6
  • 9
1

If you need only key comparison:

 _.reduce(a, function(result, value, key) {
     return b[key] === undefined ? key : []
  }, []);
gdm
  • 7,647
  • 3
  • 41
  • 71
1

We had this requirement on getting the delta between two json updates for tracking database updates. Maybe someone else can find this helpful.

https://gist.github.com/jp6rt/7fcb6907e159d7851c8d59840b669e3d

const {
  isObject,
  isEqual,
  transform,
  has,
  merge,
} = require('lodash');
const assert = require('assert');

/**
 * Perform a symmetric comparison on JSON object.
 * @param {*} baseObj - The base object to be used for comparison against the withObj.
 * @param {*} withObj - The withObject parameter is used as the comparison on the base object.
 * @param {*} invert  - Because this is a symmetric comparison. Some values in the with object
 *                      that doesn't exist on the base will be lost in translation.
 *                      You can execute again the function again with the parameters interchanged.
 *                      However you will lose the reference if the value is from the base or with
 *                      object if you intended to do an assymetric comparison.
 *                      Setting this to true will do make sure the reference is not lost.
 * @returns           - The returned object will label the result of the comparison with the
 *                      value from base and with object.
 */
const diffSym = (baseObj, withObj, invert = false) => transform(baseObj, (result, value, key) => {
  if (isEqual(value, withObj[key])
    && has(withObj, key)) {
    return;
  }

  if (isObject(value)
    && isObject(withObj[key])
    && !Array.isArray(value)) {
    result[key] = diffSym(value, withObj[key], invert);
    return;
  }

  if (!invert) {
    result[key] = {
      base: value,
      with: withObj[key],
    };
    return;
  }

  if (invert) {
    result[key] = {
      base: withObj[key],
      with: value,
    };
  }
});

/**
 * Perform a assymmetric comparison on JSON object.
 * @param {*} baseObj - The base object to be used for comparison against the withObj.
 * @param {*} withObj - The withObject parameter is used as the comparison on the base object.
 * @returns           - The returned object will label the values with
 *                      reference to the base and with object.
 */
const diffJSON = (baseObj, withObj) => {
  // Deep clone the objects so we don't update the reference objects.
  const baseObjClone = JSON.parse(JSON.stringify(baseObj));
  const withObjClone = JSON.parse(JSON.stringify(withObj));

  const beforeDelta = diffSym(baseObjClone, withObjClone);
  const afterDelta = diffSym(withObjClone, baseObjClone, true);

  return merge(afterDelta, beforeDelta);
};

// By Example:

const beforeDataObj = {
  a: 1,
  c: { d: 2, f: 3 },
  g: 4,
  h: 5,
};
const afterDataObj = {
  a: 2,
  b: 3,
  c: { d: 1, e: 1 },
  h: 5,
};

const delta = diffJSON(beforeDataObj, afterDataObj);

// Assert expected result.
assert(isEqual(delta, {
  a: { base: 1, with: 2 },
  b: { base: undefined, with: 3 },
  c: {
    d: { base: 2, with: 1 },
    e: { base: undefined, with: 1 },
    f: { base: 3, with: undefined },
  },
  g: { base: 4, with: undefined },
}));
jp6rt
  • 94
  • 1
  • 4
1

This solution returns an object with the modified attributes.

_.reduce(a, (r, v, k) => { return _.merge(r, _.isEqual(v, b[k]) ? {} : { [k]: v }); }, {});
0

While _.isEqual does deep compare of whole object, a delegate function can be composed to review specific property, like this:

isSame = (objA: any, objB: any) => (_.has(objA, 'id') && _.has(objB, 'id') ? objA['id'] === objB['id'] : false);

Use like:

const diff = _.differenceWith(sourceList, comparatorList, this.isSame);
Adam Cox
  • 3,341
  • 1
  • 36
  • 46
-1
var isEqual = function(f,s) {
  if (f === s) return true;

  if (Array.isArray(f)&&Array.isArray(s)) {
    return isEqual(f.sort(), s.sort());
  }
  if (_.isObject(f)) {
    return isEqual(f, s);
  }
  return _.isEqual(f, s);
};
Crusader
  • 1,182
  • 11
  • 22
  • This is invalid. You cannot compare objects with `===` directly, `{ a: 20 } === { a: 20 }` will return false, because it compares prototype. More right way to primarily compare objects is to wrap them into `JSON.stringify()` – IC_ Aug 13 '17 at 06:06
  • if (f === s) return true; - is only for recursion. Yes a: 20 } === { a: 20 } will return false and go to the next condition – Crusader Aug 30 '17 at 07:32
  • why not only `_.isEqual(f, s)`? :) – IC_ Aug 30 '17 at 08:46
  • This will result in an infinite recursion loop because if `f` is an object and you get to `if (_.isObject(f))` you simply go back through the function and hit that point again. Same goes for `f (Array.isArray(f)&&Array.isArray(s)) ` – rady Nov 28 '17 at 20:09
-2

this was based on @JLavoie, using lodash

let differences = function (newObj, oldObj) {
      return _.reduce(newObj, function (result, value, key) {
        if (!_.isEqual(value, oldObj[key])) {
          if (_.isArray(value)) {
            result[key] = []
            _.forEach(value, function (innerObjFrom1, index) {
              if (_.isNil(oldObj[key][index])) {
                result[key].push(innerObjFrom1)
              } else {
                let changes = differences(innerObjFrom1, oldObj[key][index])
                if (!_.isEmpty(changes)) {
                  result[key].push(changes)
                }
              }
            })
          } else if (_.isObject(value)) {
            result[key] = differences(value, oldObj[key])
          } else {
            result[key] = value
          }
        }
        return result
      }, {})
    }

https://jsfiddle.net/EmilianoBarboza/0g0sn3b9/8/

Emiliano Barboza
  • 475
  • 4
  • 11
-3

To build upon Sridhar Gudimela's answer, here it is updated in a way that uses TypeScript:

///  U T I L S

interface LooseObjectInterface {
  [key: string]: any;
};

type inputOptions = LooseObjectInterface | any[];



///  E X P O R T

export const objectCompare = (objectA: inputOptions, objectB: inputOptions): LooseObjectInterface => {
  let diffObj: LooseObjectInterface = {};

  switch(true) {
    case (Array.isArray(objectA)):
      objectA.forEach((elem: any, index: number) => {
        if (!Array.isArray(diffObj))
          diffObj = [];

        diffObj[index] = objectCompare(elem, (objectB || [])[index]);
      });

      break;

    case (objectA !== null && typeof objectA === "object"):
      Object.keys(objectA).forEach((key: any) => {
        if (Array.isArray(objectA[key])) {
          let arr = objectCompare(objectA[key], objectB[key]);

          if (!Array.isArray(arr))
            arr = [];

          arr.forEach((elem: any, index: number) => {
            if (!Array.isArray(diffObj[key]))
              diffObj[key] = [];

            diffObj[key][index] = elem;
          });
        } else if (typeof objectA[key] === "object")
          diffObj[key] = objectCompare(objectA[key], objectB[key]);
        else if (objectA[key] !== (objectB || {})[key])
          diffObj[key] = objectA[key];
        else if (objectA[key] === (objectB || {})[key])
          delete objectA[key];
      });

      break;

    default:
      break;
  }

  Object.keys(diffObj).forEach((key: any) => {
    if (typeof diffObj[key] === "object" && JSON.stringify(diffObj[key]) === "{}")
      delete diffObj[key];
  });

  return diffObj;
};

EDIT: My original answer used Flow, hence the downvotes (I assume, or maybe because my answer didn't use Lodash...however, having an answer to a similar problem can't hurt).

NetOperator Wibby
  • 1,354
  • 5
  • 22
  • 44