[javascript] Generic deep diff between two objects

I have two objects: oldObj and newObj.

The data in oldObj was used to populate a form and newObj is the result of the user changing data in this form and submitting it.

Both objects are deep, ie. they have properties that are objects or arrays of objects etc - they can be n levels deep, thus the diff algorithm needs to be recursive.

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.

So far my thoughts was to just build a genericDeepDiffBetweenObjects method that would return an object on the form {add:{...},upd:{...},del:{...}} but then I thought: somebody else must have needed this before.

So... does anyone know of a library or a piece of code that will do this and maybe have an even better way of representing the difference (in a way that is still JSON serializable)?

Update:

I have thought of a better way to represent the updated data, by using the same object structure as newObj, but turning all property values into objects on the form:

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

So if newObj.prop1 = 'new value' and oldObj.prop1 = 'old value' it would set returnObj.prop1 = {type: 'update', data: 'new value'}

Update 2:

It gets truely hairy when we get to properties that are arrays, since the array [1,2,3] should be counted as equal to [2,3,1], which is simple enough for arrays of value based types like string, int & bool, but gets really difficult to handle when it comes to arrays of reference types like objects and arrays.

Example arrays that should be found equal:

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

Not only is it quite complex to check for this type of deep value equality, but also to figure out a good way to represent the changes that might be.

This question is related to javascript object compare

The answer is


I already wrote a function for one of my projects that will comparing an object as a user options with its internal clone. It also can validate and even replace by default values if the user entered bad type of data or removed, in pure javascript.

In IE8 100% works. Tested successfully.

//  ObjectKey: ["DataType, DefaultValue"]
reference = { 
    a : ["string", 'Defaul value for "a"'],
    b : ["number", 300],
    c : ["boolean", true],
    d : {
        da : ["boolean", true],
        db : ["string", 'Defaul value for "db"'],
        dc : {
            dca : ["number", 200],
            dcb : ["string", 'Default value for "dcb"'],
            dcc : ["number", 500],
            dcd : ["boolean", true]
      },
      dce : ["string", 'Default value for "dce"'],
    },
    e : ["number", 200],
    f : ["boolean", 0],
    g : ["", 'This is an internal extra parameter']
};

userOptions = { 
    a : 999, //Only string allowed
  //b : ["number", 400], //User missed this parameter
    c: "Hi", //Only lower case or case insitive in quotes true/false allowed.
    d : {
        da : false,
        db : "HelloWorld",
        dc : {
            dca : 10,
            dcb : "My String", //Space is not allowed for ID attr
            dcc: "3thString", //Should not start with numbers
            dcd : false
      },
      dce: "ANOTHER STRING",
    },
    e: 40,
    f: true,
};


function compare(ref, obj) {

    var validation = {
        number: function (defaultValue, userValue) {
          if(/^[0-9]+$/.test(userValue))
            return userValue;
          else return defaultValue;
        },
        string: function (defaultValue, userValue) {
          if(/^[a-z][a-z0-9-_.:]{1,51}[^-_.:]$/i.test(userValue)) //This Regex is validating HTML tag "ID" attributes
            return userValue;
          else return defaultValue;
        },
        boolean: function (defaultValue, userValue) {
          if (typeof userValue === 'boolean')
            return userValue;
          else return defaultValue;
        }
    };

    for (var key in ref)
        if (obj[key] && obj[key].constructor && obj[key].constructor === Object)
          ref[key] = compare(ref[key], obj[key]);
        else if(obj.hasOwnProperty(key))
          ref[key] = validation[ref[key][0]](ref[key][1], obj[key]); //or without validation on user enties => ref[key] = obj[key]
        else ref[key] = ref[key][1];
    return ref;
}

//console.log(
    alert(JSON.stringify( compare(reference, userOptions),null,2 ))
//);

/* result

{
  "a": "Defaul value for \"a\"",
  "b": 300,
  "c": true,
  "d": {
    "da": false,
    "db": "Defaul value for \"db\"",
    "dc": {
      "dca": 10,
      "dcb": "Default value for \"dcb\"",
      "dcc": 500,
      "dcd": false
    },
    "dce": "Default value for \"dce\""
  },
  "e": 40,
  "f": true,
  "g": "This is an internal extra parameter"
}

*/

const diff = require("deep-object-diff").diff;
let differences = diff(obj2, obj1);

There is an npm module with over 500k weekly downloads: https://www.npmjs.com/package/deep-object-diff

I like the object like representation of the differences - especially it is easy to see the structure, when it is formated.

const diff = require("deep-object-diff").diff;

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'
}
*/

The more extended and simplified function from sbgoran's answer.
This allow deep scan and find an array's simillarity.

_x000D_
_x000D_
var result = objectDifference({_x000D_
      a:'i am unchanged',_x000D_
      b:'i am deleted',_x000D_
      e: {a: 1,b:false, c: null},_x000D_
      f: [1,{a: 'same',b:[{a:'same'},{d: 'delete'}]}],_x000D_
      g: new Date('2017.11.25'),_x000D_
      h: [1,2,3,4,5]_x000D_
  },_x000D_
  {_x000D_
      a:'i am unchanged',_x000D_
      c:'i am created',_x000D_
      e: {a: '1', b: '', d:'created'},_x000D_
      f: [{a: 'same',b:[{a:'same'},{c: 'create'}]},1],_x000D_
      g: new Date('2017.11.25'),_x000D_
      h: [4,5,6,7,8]_x000D_
  });_x000D_
console.log(result);_x000D_
_x000D_
function objectDifference(obj1, obj2){_x000D_
    if((dataType(obj1) !== 'array' && dataType(obj1) !== 'object') || (dataType(obj2) !== 'array' && dataType(obj2) !== 'object')){_x000D_
        var type = '';_x000D_
_x000D_
        if(obj1 === obj2 || (dataType(obj1) === 'date' && dataType(obj2) === 'date' && obj1.getTime() === obj2.getTime()))_x000D_
            type = 'unchanged';_x000D_
        else if(dataType(obj1) === 'undefined')_x000D_
            type = 'created';_x000D_
        if(dataType(obj2) === 'undefined')_x000D_
            type = 'deleted';_x000D_
        else if(type === '') type = 'updated';_x000D_
_x000D_
        return {_x000D_
            type: type,_x000D_
            data:(obj1 === undefined) ? obj2 : obj1_x000D_
        };_x000D_
    }_x000D_
  _x000D_
    if(dataType(obj1) === 'array' && dataType(obj2) === 'array'){_x000D_
        var diff = [];_x000D_
        obj1.sort(); obj2.sort();_x000D_
        for(var i = 0; i < obj2.length; i++){_x000D_
            var type = obj1.indexOf(obj2[i]) === -1?'created':'unchanged';_x000D_
            if(type === 'created' && (dataType(obj2[i]) === 'array' || dataType(obj2[i]) === 'object')){_x000D_
                diff.push(_x000D_
                    objectDifference(obj1[i], obj2[i])_x000D_
                );_x000D_
                continue;_x000D_
            }_x000D_
            diff.push({_x000D_
                type: type,_x000D_
                data: obj2[i]_x000D_
            });_x000D_
        }_x000D_
_x000D_
        for(var i = 0; i < obj1.length; i++){_x000D_
            if(obj2.indexOf(obj1[i]) !== -1 || dataType(obj1[i]) === 'array' || dataType(obj1[i]) === 'object')_x000D_
                continue;_x000D_
            diff.push({_x000D_
                type: 'deleted',_x000D_
                data: obj1[i]_x000D_
            });_x000D_
        }_x000D_
    } else {_x000D_
        var diff = {};_x000D_
        var key = Object.keys(obj1);_x000D_
        for(var i = 0; i < key.length; i++){_x000D_
            var value2 = undefined;_x000D_
            if(dataType(obj2[key[i]]) !== 'undefined')_x000D_
                value2 = obj2[key[i]];_x000D_
_x000D_
            diff[key[i]] = objectDifference(obj1[key[i]], value2);_x000D_
        }_x000D_
_x000D_
        var key = Object.keys(obj2);_x000D_
        for(var i = 0; i < key.length; i++){_x000D_
            if(dataType(diff[key[i]]) !== 'undefined')_x000D_
                continue;_x000D_
_x000D_
            diff[key[i]] = objectDifference(undefined, obj2[key[i]]);_x000D_
        }_x000D_
    }_x000D_
_x000D_
    return diff;_x000D_
}_x000D_
_x000D_
function dataType(data){_x000D_
    if(data === undefined || data === null) return 'undefined';_x000D_
    if(data.constructor === String) return 'string';_x000D_
    if(data.constructor === Array) return 'array';_x000D_
    if(data.constructor === Object) return 'object';_x000D_
    if(data.constructor === Number) return 'number';_x000D_
    if(data.constructor === Boolean) return 'boolean';_x000D_
    if(data.constructor === Function) return 'function';_x000D_
    if(data.constructor === Date) return 'date';_x000D_
    if(data.constructor === RegExp) return 'regex';_x000D_
    return 'unknown';_x000D_
}
_x000D_
_x000D_
_x000D_


I know I'm late to the party, but I needed something similar that the above answers didn't help.

I was using Angular's $watch function to detect changes in a variable. Not only did I need to know whether a property had changed on the variable, but I also wanted to make sure that the property that changed was not a temporary, calculated field. In other words, I wanted to ignore certain properties.

Here's the code: https://jsfiddle.net/rv01x6jo/

Here's how to use it:

// To only return the difference
var difference = diff(newValue, oldValue);  

// To exclude certain properties
var difference = diff(newValue, oldValue, [newValue.prop1, newValue.prop2, newValue.prop3]);

Hope this helps someone.


Here is a modified version of something found on gisthub.

isNullBlankOrUndefined = function (o) {
    return (typeof o === "undefined" || o == null || o === "");
}

/**
 * Deep diff between two object, using lodash
 * @param  {Object} object Object compared
 * @param  {Object} base   Object to compare with
 * @param  {Object} ignoreBlanks will not include properties whose value is null, undefined, etc.
 * @return {Object}        Return a new object who represent the diff
 */
objectDifference = function (object, base, ignoreBlanks = false) {
    if (!lodash.isObject(object) || lodash.isDate(object)) return object            // special case dates
    return lodash.transform(object, (result, value, key) => {
        if (!lodash.isEqual(value, base[key])) {
            if (ignoreBlanks && du.isNullBlankOrUndefined(value) && isNullBlankOrUndefined( base[key])) return;
            result[key] = lodash.isObject(value) && lodash.isObject(base[key]) ? objectDifference(value, base[key]) : value;
        }
    });
}

I composed this for my own use-case (es5 environment), thought this might be useful for someone, so here it is:

function deepCompare(obj1, obj2) {
    var diffObj = Array.isArray(obj2) ? [] : {}
    Object.getOwnPropertyNames(obj2).forEach(function(prop) {
        if (typeof obj2[prop] === 'object') {
            diffObj[prop] = deepCompare(obj1[prop], obj2[prop])
            // if it's an array with only length property => empty array => delete
            // or if it's an object with no own properties => delete
            if (Array.isArray(diffObj[prop]) && Object.getOwnPropertyNames(diffObj[prop]).length === 1 || Object.getOwnPropertyNames(diffObj[prop]).length === 0) {
                delete diffObj[prop]
            }
        } else if(obj1[prop] !== obj2[prop]) {
            diffObj[prop] = obj2[prop]
        }
    });
    return diffObj
}

This might be not really efficient, but will output an object with only different props based on second Obj.


I stumbled here trying to look for a way to get the difference between two objects. This is my solution using Lodash:

// Get updated values (including new values)
var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value));

// Get updated values (excluding new values)
var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value)));

// Get old values (by using updated values)
var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {});

// Get newly added values
var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key));

// Get removed values
var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key));

// Then you can group them however you want with the result

Code snippet below:

_x000D_
_x000D_
var last = {_x000D_
"authed": true,_x000D_
"inForeground": true,_x000D_
"goodConnection": false,_x000D_
"inExecutionMode": false,_x000D_
"online": true,_x000D_
"array": [1, 2, 3],_x000D_
"deep": {_x000D_
 "nested": "value",_x000D_
},_x000D_
"removed": "value",_x000D_
};_x000D_
_x000D_
var curr = {_x000D_
"authed": true,_x000D_
"inForeground": true,_x000D_
"deep": {_x000D_
 "nested": "changed",_x000D_
},_x000D_
"array": [1, 2, 4],_x000D_
"goodConnection": true,_x000D_
"inExecutionMode": false,_x000D_
"online": false,_x000D_
"new": "value"_x000D_
};_x000D_
_x000D_
// Get updated values (including new values)_x000D_
var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value));_x000D_
// Get updated values (excluding new values)_x000D_
var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value)));_x000D_
// Get old values (by using updated values)_x000D_
var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {});_x000D_
// Get newly added values_x000D_
var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key));_x000D_
// Get removed values_x000D_
var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key));_x000D_
_x000D_
console.log('oldValues', JSON.stringify(oldValues));_x000D_
console.log('updatedValuesIncl', JSON.stringify(updatedValuesIncl));_x000D_
console.log('updatedValuesExcl', JSON.stringify(updatedValuesExcl));_x000D_
console.log('newCreatedValues', JSON.stringify(newCreatedValues));_x000D_
console.log('deletedValues', JSON.stringify(deletedValues));
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
_x000D_
_x000D_
_x000D_


I took the answer above by @sbgoran and modified it for my case same as the question needed, to treat arrays as sets (i.e. order is not important for diff)

const deepDiffMapper = function () {
return {
  VALUE_CREATED: "created",
  VALUE_UPDATED: "updated",
  VALUE_DELETED: "deleted",
  VALUE_UNCHANGED: "unchanged",
  map: function(obj1: any, obj2: any) {
    if (this.isFunction(obj1) || this.isFunction(obj2)) {
      throw "Invalid argument. Function given, object expected.";
    }
    if (this.isValue(obj1) || this.isValue(obj2)) {
      return {
        type: this.compareValues(obj1, obj2),
        data: obj2 === undefined ? obj1 : obj2
      };
    }

    if (this.isArray(obj1) || this.isArray(obj2)) {
      return {
        type: this.compareArrays(obj1, obj2),
        data: this.getArrayDiffData(obj1, obj2)
      };
    }

    const diff: any = {};
    for (const key in obj1) {

      if (this.isFunction(obj1[key])) {
        continue;
      }

      let value2 = undefined;
      if (obj2[key] !== undefined) {
        value2 = obj2[key];
      }

      diff[key] = this.map(obj1[key], value2);
    }
    for (const key in obj2) {
      if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
        continue;
      }

      diff[key] = this.map(undefined, obj2[key]);
    }

    return diff;

  },

  getArrayDiffData: function(arr1: Array<any>, arr2: Array<any>) {
    const set1 = new Set(arr1);
    const set2 = new Set(arr2);

    if (arr1 === undefined || arr2 === undefined) {
       return arr1 === undefined ? arr1 : arr2;
    }
    const deleted = [...arr1].filter(x => !set2.has(x));

    const added = [...arr2].filter(x => !set1.has(x));

    return {
      added, deleted
    };

  },

  compareArrays: function(arr1: Array<any>, arr2: Array<any>) {
    const set1 = new Set(arr1);
    const set2 = new Set(arr2);
    if (_.isEqual(_.sortBy(arr1), _.sortBy(arr2))) {
      return this.VALUE_UNCHANGED;
    }
    if (arr1 === undefined) {
      return this.VALUE_CREATED;
    }
    if (arr2 === undefined) {
      return this.VALUE_DELETED;
    }
    return this.VALUE_UPDATED;
  },
  compareValues: function (value1: any, value2: any) {
    if (value1 === value2) {
      return this.VALUE_UNCHANGED;
    }
    if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
      return this.VALUE_UNCHANGED;
    }
    if (value1 === undefined) {
      return this.VALUE_CREATED;
    }
    if (value2 === undefined) {
      return this.VALUE_DELETED;
    }
    return this.VALUE_UPDATED;
  },
  isFunction: function (x: any) {
    return Object.prototype.toString.call(x) === "[object Function]";
  },
  isArray: function (x: any) {
    return Object.prototype.toString.call(x) === "[object Array]";
  },
  isDate: function (x: any) {
    return Object.prototype.toString.call(x) === "[object Date]";
  },
  isObject: function (x: any) {
    return Object.prototype.toString.call(x) === "[object Object]";
  },
  isValue: function (x: any) {
    return !this.isObject(x) && !this.isArray(x);
  }
 };
}();

Here is a JavaScript library which you can use for finding diff between two JavaScript objects:

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

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

You can use recursive-diff library in browser as well as Node.js. For browser, do the following:

<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>

Whereas in node.js you can require 'recursive-diff' module and use it like below:

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

Using Underscore, a simple diff:

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

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

Results in the parts of o1 that correspond but with different values in o2:

{a: 1, b: 2}

It'd be different for a deep diff:

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;
}

As pointed out by @Juhana in the comments, the above is only a diff a-->b and not reversible (meaning extra properties in b would be ignored). Use instead 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());

See http://jsfiddle.net/drzaus/9g5qoxwj/ for full example+tests+mixins


I modified @sbgoran's answer so that the resulting diff object includes only the changed values, and omits values that were the same. In addition, it shows both the original value and the updated value.

var deepDiffMapper = function () {
    return {
        VALUE_CREATED: 'created',
        VALUE_UPDATED: 'updated',
        VALUE_DELETED: 'deleted',
        VALUE_UNCHANGED: '---',
        map: function (obj1, obj2) {
            if (this.isFunction(obj1) || this.isFunction(obj2)) {
                throw 'Invalid argument. Function given, object expected.';
            }
            if (this.isValue(obj1) || this.isValue(obj2)) {
                let returnObj = {
                    type: this.compareValues(obj1, obj2),
                    original: obj1,
                    updated: obj2,
                };
                if (returnObj.type != this.VALUE_UNCHANGED) {
                    return returnObj;
                }
                return undefined;
            }

            var diff = {};
            let foundKeys = {};
            for (var key in obj1) {
                if (this.isFunction(obj1[key])) {
                    continue;
                }

                var value2 = undefined;
                if (obj2[key] !== undefined) {
                    value2 = obj2[key];
                }

                let mapValue = this.map(obj1[key], value2);
                foundKeys[key] = true;
                if (mapValue) {
                    diff[key] = mapValue;
                }
            }
            for (var key in obj2) {
                if (this.isFunction(obj2[key]) || foundKeys[key] !== undefined) {
                    continue;
                }

                let mapValue = this.map(undefined, obj2[key]);
                if (mapValue) {
                    diff[key] = mapValue;
                }
            }

            //2020-06-13: object length code copied from https://stackoverflow.com/a/13190981/2336212
            if (Object.keys(diff).length > 0) {
                return diff;
            }
            return undefined;
        },
        compareValues: function (value1, value2) {
            if (value1 === value2) {
                return this.VALUE_UNCHANGED;
            }
            if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
                return this.VALUE_UNCHANGED;
            }
            if (value1 === undefined) {
                return this.VALUE_CREATED;
            }
            if (value2 === undefined) {
                return this.VALUE_DELETED;
            }
            return this.VALUE_UPDATED;
        },
        isFunction: function (x) {
            return Object.prototype.toString.call(x) === '[object Function]';
        },
        isArray: function (x) {
            return Object.prototype.toString.call(x) === '[object Array]';
        },
        isDate: function (x) {
            return Object.prototype.toString.call(x) === '[object Date]';
        },
        isObject: function (x) {
            return Object.prototype.toString.call(x) === '[object Object]';
        },
        isValue: function (x) {
            return !this.isObject(x) && !this.isArray(x);
        }
    }
}();

Here is a typescript version of @sbgoran code

export class deepDiffMapper {

  static VALUE_CREATED = 'created';
  static VALUE_UPDATED = 'updated';
  static VALUE_DELETED = 'deleted';
  static VALUE_UNCHANGED ='unchanged';

  protected isFunction(obj: object) {
    return {}.toString.apply(obj) === '[object Function]';
  };

  protected isArray(obj: object) {
      return {}.toString.apply(obj) === '[object Array]';
  };

  protected isObject(obj: object) {
      return {}.toString.apply(obj) === '[object Object]';
  };

  protected isDate(obj: object) {
      return {}.toString.apply(obj) === '[object Date]';
  };

  protected isValue(obj: object) {
      return !this.isObject(obj) && !this.isArray(obj);
  };

  protected compareValues (value1: any, value2: any) {
    if (value1 === value2) {
        return deepDiffMapper.VALUE_UNCHANGED;
    }
    if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
        return deepDiffMapper.VALUE_UNCHANGED;
    }
    if ('undefined' == typeof(value1)) {
        return deepDiffMapper.VALUE_CREATED;
    }
    if ('undefined' == typeof(value2)) {
        return deepDiffMapper.VALUE_DELETED;
    }

    return deepDiffMapper.VALUE_UPDATED;
  }

  public map(obj1: object, obj2: object) {
      if (this.isFunction(obj1) || this.isFunction(obj2)) {
          throw 'Invalid argument. Function given, object expected.';
      }
      if (this.isValue(obj1) || this.isValue(obj2)) {
          return {
              type: this.compareValues(obj1, obj2),
              data: (obj1 === undefined) ? obj2 : obj1
          };
      }

      var diff = {};
      for (var key in obj1) {
          if (this.isFunction(obj1[key])) {
              continue;
          }

          var value2 = undefined;
          if ('undefined' != typeof(obj2[key])) {
              value2 = obj2[key];
          }

          diff[key] = this.map(obj1[key], value2);
      }
      for (var key in obj2) {
          if (this.isFunction(obj2[key]) || ('undefined' != typeof(diff[key]))) {
              continue;
          }

          diff[key] = this.map(undefined, obj2[key]);
      }

      return diff;

  }
}

I have used this piece of code for doing the task that you describe:

function mergeRecursive(obj1, obj2) {
    for (var p in obj2) {
        try {
            if(obj2[p].constructor == Object) {
                obj1[p] = mergeRecursive(obj1[p], obj2[p]);
            }
            // Property in destination object set; update its value.
            else if (Ext.isArray(obj2[p])) {
                // obj1[p] = [];
                if (obj2[p].length < 1) {
                    obj1[p] = obj2[p];
                }
                else {
                    obj1[p] = mergeRecursive(obj1[p], obj2[p]);
                }

            }else{
                obj1[p] = obj2[p];
            }
        } catch (e) {
            // Property in destination object not set; create it and set its value.
            obj1[p] = obj2[p];
        }
    }
    return obj1;
}

this will get you a new object that will merge all the changes between the old object and the new object from your form


I've developed the Function named "compareValue()" in Javascript. it returns whether the value is same or not. I've called compareValue() in for loop of one Object. you can get difference of two objects in diffParams.

_x000D_
_x000D_
var diffParams = {};_x000D_
var obj1 = {"a":"1", "b":"2", "c":[{"key":"3"}]},_x000D_
    obj2 = {"a":"1", "b":"66", "c":[{"key":"55"}]};_x000D_
_x000D_
for( var p in obj1 ){_x000D_
  if ( !compareValue(obj1[p], obj2[p]) ){_x000D_
    diffParams[p] = obj1[p];_x000D_
  }_x000D_
}_x000D_
_x000D_
function compareValue(val1, val2){_x000D_
  var isSame = true;_x000D_
  for ( var p in val1 ) {_x000D_
_x000D_
    if (typeof(val1[p]) === "object"){_x000D_
      var objectValue1 = val1[p],_x000D_
          objectValue2 = val2[p];_x000D_
      for( var value in objectValue1 ){_x000D_
        isSame = compareValue(objectValue1[value], objectValue2[value]);_x000D_
        if( isSame === false ){_x000D_
          return false;_x000D_
        }_x000D_
      }_x000D_
    }else{_x000D_
      if(val1 !== val2){_x000D_
        isSame = false;_x000D_
      }_x000D_
    }_x000D_
  }_x000D_
  return isSame;_x000D_
}_x000D_
console.log(diffParams);
_x000D_
_x000D_
_x000D_


Using Lodash:

_x000D_
_x000D_
_.mergeWith(oldObj, newObj, function (objectValue, sourceValue, key, object, source) {_x000D_
    if ( !(_.isEqual(objectValue, sourceValue)) && (Object(objectValue) !== objectValue)) {_x000D_
        console.log(key + "\n    Expected: " + sourceValue + "\n    Actual: " + objectValue);_x000D_
    }_x000D_
});
_x000D_
_x000D_
_x000D_

I don't use key/object/source but I left it in there if you need to access them. The object comparison just prevents the console from printing the differences to the console from the outermost element to the innermost element.

You can add some logic inside to handle arrays. Perhaps sort the arrays first. This is a very flexible solution.

EDIT

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


These days, there are quite a few modules available for this. I recently wrote a module to do this, because I wasn't satisfied with the numerous diffing modules I found. Its called odiff: https://github.com/Tixit/odiff . I also listed a bunch of the most popular modules and why they weren't acceptable in the readme of odiff, which you could take a look through if odiff doesn't have the properties you want. Here's an example:

var a = [{a:1,b:2,c:3},              {x:1,y: 2, z:3},              {w:9,q:8,r:7}]
var b = [{a:1,b:2,c:3},{t:4,y:5,u:6},{x:1,y:'3',z:3},{t:9,y:9,u:9},{w:9,q:8,r:7}]

var diffs = odiff(a,b)

/* diffs now contains:
[{type: 'add', path:[], index: 2, vals: [{t:9,y:9,u:9}]},
 {type: 'set', path:[1,'y'], val: '3'},
 {type: 'add', path:[], index: 1, vals: [{t:4,y:5,u:6}]}
]
*/

you can simply do:

const objectDiff = (a, b)  => _.fromPairs(_.differenceWith(_.toPairs(a), _.toPairs(b), _.isEqual))

Here is a solution that is:

  • Typescript (but easily convertible to Javascript)
  • have no lib dependencies
  • generic, and doesn't care about checking object types (aside the object type)
  • supports properties with value undefined
  • deep of not (default)

First we define the comparison result interface:

export interface ObjectComparison {
  added: {};
  updated: {
    [propName: string]: Change;
  };
  removed: {};
  unchanged: {};
}

with the special case of change where we want to know what are old and new values:

export interface Change {
  oldValue: any;
  newValue: any;
}

Then we can provide the diff function which is merely two loops (with recursivity if deep is true):

export class ObjectUtils {

  static diff(o1: {}, o2: {}, deep = false): ObjectComparison {
    const added = {};
    const updated = {};
    const removed = {};
    const unchanged = {};
    for (const prop in o1) {
      if (o1.hasOwnProperty(prop)) {
        const o2PropValue = o2[prop];
        const o1PropValue = o1[prop];
        if (o2.hasOwnProperty(prop)) {
          if (o2PropValue === o1PropValue) {
            unchanged[prop] = o1PropValue;
          } else {
            updated[prop] = deep && this.isObject(o1PropValue) && this.isObject(o2PropValue) ? this.diff(o1PropValue, o2PropValue, deep) : {newValue: o2PropValue};
          }
        } else {
          removed[prop] = o1PropValue;
        }
      }
    }
    for (const prop in o2) {
      if (o2.hasOwnProperty(prop)) {
        const o1PropValue = o1[prop];
        const o2PropValue = o2[prop];
        if (o1.hasOwnProperty(prop)) {
          if (o1PropValue !== o2PropValue) {
            if (!deep || !this.isObject(o1PropValue)) {
              updated[prop].oldValue = o1PropValue;
            }
          }
        } else {
          added[prop] = o2PropValue;
        }
      }
    }
    return { added, updated, removed, unchanged };
  }

  /**
   * @return if obj is an Object, including an Array.
   */
  static isObject(obj: any) {
    return obj !== null && typeof obj === 'object';
  }
}

As an example, calling:

ObjectUtils.diff(
  {
    a: 'a', 
    b: 'b', 
    c: 'c', 
    arr: ['A', 'B'], 
    obj: {p1: 'p1', p2: 'p2'}
  },
  {
    b: 'x', 
    c: 'c', 
    arr: ['B', 'C'], 
    obj: {p2: 'p2', p3: 'p3'}, 
    d: 'd'
  },
);

will return:

{
  added: {d: 'd'},
  updated: {
    b: {oldValue: 'b', newValue: 'x'},
    arr: {oldValue: ['A', 'B'], newValue: ['B', 'C']},
    obj: {oldValue: {p1: 'p1', p2: 'p2'}, newValue: {p2: 'p2', p3: 'p3'}}
  },
  removed: {a: 'a'},
  unchanged: {c: 'c'},
}

and calling the same with the deep third parameter will return:

{
  added: {d: 'd'},
  updated: {
    b: {oldValue: 'b', newValue: 'x'},
    arr: {
      added: {},
      removed: {},
      unchanged: {},
      updated: {
        0: {oldValue: 'A', newValue: 'B'},
        1: {oldValue: 'B', newValue: 'C', }
      }
    },
    obj: {
      added: {p3: 'p3'},
      removed: {p1: 'p1'},
      unchanged: {p2: 'p2'},
      updated: {}
    }
  },
  removed: {a: 'a'},
  unchanged: {c: 'c'},
}

I just use ramda, for resolve the same problem, i need to know what is changed in new object. So here my design.

const oldState = {id:'170',name:'Ivab',secondName:'Ivanov',weight:45};
const newState = {id:'170',name:'Ivanko',secondName:'Ivanov',age:29};

const keysObj1 = R.keys(newState)

const filterFunc = key => {
  const value = R.eqProps(key,oldState,newState)
  return {[key]:value}
}

const result = R.map(filterFunc, keysObj1)

result is, name of property and it's status.

[{"id":true}, {"name":false}, {"secondName":true}, {"age":false}]

I'd like to offer an ES6 solution...This is a one-way diff, meaning that it will return keys/values from o2 that are not identical to 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]
  }
}, {})