Coder Perfect

Deep difference between two items in a generic way

Problem

oldObj and newObj are the two objects I have.

The data in oldObj was used to fill out a form, and newObj is the outcome of the user amending the data and submitting the form.

Both objects are deep, in the sense that they have characteristics that are objects or arrays of objects, and they can be n levels deep, necessitating a recursive diff technique.

Now I need to not just figure out what was changed (as in added/updated/deleted) from oldObj to newObj, but also how to best represent it.

My initial notion was to create a genericDeepDiffBetweenObjects method that would return an object of the form add:…,upd:…,del:…, but then I realized that this must have been needed by someone else.

So, does anyone know of a library or a piece of code that will handle this, as well as a better manner of representing the difference (that is still JSON serializable)?

I came up with a better approach to display the updated data by utilizing the same object structure as newObj but converting all property values on the form into objects:

{type: '<update|create|delete>', data: <propertyValue>}

If newObj.prop1 = ‘new value’ and oldObj.prop1 = ‘old value,’ returnObj.prop1 = type: ‘update,’ data: ‘new value’ would be set.

It gets pretty complex when we get to array properties, because the array [1,2,3] should be counted as equal to [2,3,1], which is simple enough for value-based types like string, int, and bool, but really difficult to handle when it comes to reference types like objects and arrays.

The following are some examples of arrays that should be found equal:

[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]

It’s difficult not only to check for deep value equality, but also to figure out a decent manner to express any changes that may occur.

Asked by Martin Jespersen

Solution #1

I created a small class that does exactly what you want; you can try it out here.

Only thing that is different from your proposal is that I don’t consider

[1,[{c: 1},2,3],{a:'hey'}]

and

[{a:'hey'},1,[3,{c: 1},2]]

to be the same, because I believe that arrays are not equal if their elements are not ordered in the same order. Of course, if necessary, this can be altered. This code can also be improved by passing a function as an input, which will be used to format the diff object in any way based on the primitive values supplied in (now this job is done by “compareValues” method).

Answered by sbgoran

Solution #2

A simple diff using Underscore:

var o1 = {a: 1, b: 2, c: 2},
    o2 = {a: 2, b: 1, c: 2};

_.omit(o1, function(v,k) { return o2[k] === v; })

The following are the results in areas of o1 that coincide but have distinct values in o2:

{a: 1, b: 2}

For a deep diff, it’d be different:

function diff(a,b) {
    var r = {};
    _.each(a, function(v,k) {
        if(b[k] === v) return;
        // but what if it returns an empty object? still attach?
        r[k] = _.isObject(v)
                ? _.diff(v, b[k])
                : v
            ;
        });
    return r;
}

The above is merely a diff a—>b and is not reversible, as @Juhana pointed out in the comments (meaning extra properties in b would be ignored). Instead, use a—>b—>a:

(function(_) {
  function deepDiff(a, b, r) {
    _.each(a, function(v, k) {
      // already checked this or equal...
      if (r.hasOwnProperty(k) || b[k] === v) return;
      // but what if it returns an empty object? still attach?
      r[k] = _.isObject(v) ? _.diff(v, b[k]) : v;
    });
  }

  /* the function */
  _.mixin({
    diff: function(a, b) {
      var r = {};
      deepDiff(a, b, r);
      deepDiff(b, a, r);
      return r;
    }
  });
})(_.noConflict());

Full example+tests+mixins may be found at http://jsfiddle.net/drzaus/9g5qoxwj/.

Answered by drzaus

Solution #3

I’d like to propose a solution based on ES6… This is a one-way diff, which means it will return keys/values from o2 that aren’t exactly the same as their counterparts in o1:

let o1 = {
  one: 1,
  two: 2,
  three: 3
}

let o2 = {
  two: 2,
  three: 3,
  four: 4
}

let diff = Object.keys(o2).reduce((diff, key) => {
  if (o1[key] === o2[key]) return diff
  return {
    ...diff,
    [key]: o2[key]
  }
}, {})

Answered by senornestor

Solution #4

Using Lodash:

I don’t use key/object/source, but I included it in case you need it. The object comparison just prohibits the console from printing the differences between the outermost and innermost elements to the console.

To handle arrays, you can write some logic inside. Sort the arrays first, if possible. This is a really adaptable solution.

Changed from _.merge to _.mergeWith due to lodash update. Thanks Aviron for noticing the change.

Answered by toshiomagic

Solution #5

A JavaScript library for identifying differences between two JavaScript objects is available here:

Github URL: https://github.com/cosmicanant/recursive-diff

Npmjs url: https://www.npmjs.com/package/recursive-diff

The recursive-diff library can be used in a Node.js-based server side application as well as in the browser. It can be used in a browser as follows:

<script type="text" src="https://unpkg.com/recursive-diff@latest/dist/recursive-diff.min.js"/>
<script type="text/javascript">
     const ob1 = {a:1, b: [2,3]};
     const ob2 = {a:2, b: [3,3,1]};
     const delta = recursiveDiff.getDiff(ob1,ob2); 
     /* console.log(delta) will dump following data 
     [
         {path: ['a'], op: 'update', val: 2}
         {path: ['b', '0'], op: 'update',val: 3},
         {path: ['b',2], op: 'add', val: 1 },
     ]
      */
     const ob3 = recursiveDiff.applyDiff(ob1, delta); //expect ob3 is deep equal to ob2
 </script>

In a node.js application, however, it can be used as follows:

const diff = require('recursive-diff');
const ob1 = {a: 1}, ob2: {b:2};
const diff = diff.getDiff(ob1, ob2);

Answered by Anant

Post is based on https://stackoverflow.com/questions/8572826/generic-deep-diff-between-two-objects