web-dev-qa-db-fra.com

Diff profond générique entre deux objets

J'ai deux objets: oldObj et newObj.

Les données de oldObj ont été utilisées pour renseigner un formulaire et newObj est le résultat du fait que l'utilisateur a modifié les données de ce formulaire et les a soumises.

Les deux objets sont profonds, c'est à dire. ils ont des propriétés qui sont des objets ou des tableaux d'objets, etc. - ils peuvent avoir n niveaux, l'algorithme diff doit donc être récursif.

Maintenant, je dois non seulement comprendre ce qui a été changé (comme ajouté/mis à jour/supprimé) de oldObj à newObj, mais aussi comment le représenter au mieux.

Jusqu'ici, je pensais simplement construire une méthode genericDeepDiffBetweenObjects qui renverrait un objet sous la forme {add:{...},upd:{...},del:{...}} mais je me suis dit que quelqu'un d'autre en aurait eu besoin auparavant.

Alors ... est-ce que quelqu'un connaît une bibliothèque ou un morceau de code qui le fera et qui aura peut-être un moyen encore meilleur de représenter la différence (d'une manière toujours sérialisable JSON)?

Mise à jour:

J'ai pensé à un meilleur moyen de représenter les données mises à jour, en utilisant la même structure d'objet que newObj, mais en transformant toutes les valeurs de propriétés en objets sur le formulaire:

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

Donc, si newObj.prop1 = 'new value' et oldObj.prop1 = 'old value', il définirait returnObj.prop1 = {type: 'update', data: 'new value'}

Mise à jour 2:

Cela devient vraiment poilu quand nous arrivons aux propriétés qui sont des tableaux, puisque le tableau [1,2,3] devrait être compté comme égal à [2,3,1], ce qui est assez simple pour les tableaux de types basés sur la valeur comme string, int & bool, mais devient vraiment difficile à gérer quand il s'agit de tableaux de types de référence tels que des objets et des tableaux.

Exemple de tableaux qui devraient être trouvés égaux:

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

Il est non seulement assez complexe de vérifier ce type d’égalité de valeur profonde, mais également de trouver un bon moyen de représenter les changements qui pourraient être.

184
Martin Jespersen

J'ai écrit un petit cours qui fait ce que tu veux, tu peux le tester ici .

La seule chose qui diffère de votre proposition est que je ne considère pas que [1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]] soit identique, car je pense que les tableaux ne sont pas égaux si l'ordre de leurs éléments n'est pas identique. Bien sûr, cela peut être changé si nécessaire. Ce code peut également être amélioré pour prendre la fonction en tant qu'argument qui sera utilisé pour formater un objet diff de manière arbitraire en fonction des valeurs primitives transmises (ce travail est maintenant effectué par la méthode "compareValues").

var deepDiffMapper = function () {
  return {
    VALUE_CREATED: 'created',
    VALUE_UPDATED: 'updated',
    VALUE_DELETED: 'deleted',
    VALUE_UNCHANGED: '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)) {
        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 (obj2[key] !== undefined) {
          value2 = obj2[key];
        }

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

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

      return diff;

    },
    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);
    }
  }
}();


var result = deepDiffMapper.map({
  a: 'i am unchanged',
  b: 'i am deleted',
  e: {
    a: 1,
    b: false,
    c: null
  },
  f: [1, {
    a: 'same',
    b: [{
      a: 'same'
    }, {
      d: 'delete'
    }]
  }],
  g: new Date('2017.11.25')
}, {
  a: 'i am unchanged',
  c: 'i am created',
  e: {
    a: '1',
    b: '',
    d: 'created'
  },
  f: [{
    a: 'same',
    b: [{
      a: 'same'
    }, {
      c: 'create'
    }]
  }, 1],
  g: new Date('2017.11.25')
});
console.log(result);
118
sbgoran

En utilisant Underscore, un 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; })

Résultats dans les parties de o1 qui correspondent mais avec des valeurs différentes dans o2:

{a: 1, b: 2}

Ce serait différent pour un diff profond:

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

Comme l'a souligné @Juhana dans les commentaires, ce qui précède n'est qu'un diff a -> b et non réversible (ce qui signifie que des propriétés supplémentaires dans b seraient ignoré). Utilisez à la place 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());

Voir http://jsfiddle.net/drzaus/9g5qoxwj/ pour un exemple complet + tests + mixins

83
drzaus

J'aimerais proposer une solution ES6 ... Il s'agit d'un diff à sens unique, ce qui signifie qu'il retournera des clés/valeurs de o2 qui ne sont pas identiques à leurs équivalents dans 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]
  }
}, {})
38
senornestor

Utiliser Lodash:

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

Je n'utilise pas clé/objet/source mais je l'ai laissé là si vous devez y accéder. La comparaison d'objet empêche simplement la console d'imprimer les différences sur la console de l'élément le plus externe à l'élément le plus à l'intérieur.

Vous pouvez ajouter une logique à l'intérieur pour gérer les tableaux. Peut-être trier les tableaux en premier. C'est une solution très flexible.

MODIFIER

Modifié de _.merge à _.mergeWith en raison de la mise à jour de lodash. Merci Aviron d'avoir remarqué le changement.

21
toshiomagic

Voici une bibliothèque JavaScript que vous pouvez utiliser pour rechercher des différences entre deux objets JavaScript:

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

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

recursiveDiff

Vous pouvez utiliser la bibliothèque recursive-diff dans le navigateur, ainsi que Node.js. Pour le navigateur, procédez comme suit:

<script type="text" src="index.js"/>
<script type="text/javascript">
     var ob1 = {a:1, b: [2,3]};
     var ob2 = {a:2, b: [3,3,1]};
     var delta = diff.getDiff(ob1,ob2); 
     /* console.log(delta) will dump following data 
     {
         '/a':{operation:'update',value:2}
         '/b/0':{operation:'update',value:3},
         '/b/2':{operation:'add',value:1},
     } */
     var ob3 = diff.applyDiff(ob1, delta); //expect ob3 is deep equal to ob2
 </script>

Alors que dans node.js vous pouvez avoir besoin du module 'récursif-diff' et l'utiliser comme ci-dessous:

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

Ces jours-ci, il y a pas mal de modules disponibles pour cela. J'ai récemment écrit un module pour cela, parce que je n'étais pas satisfait des nombreux modules de diffing que j'ai trouvés. Son appelé odiff: https://github.com/Tixit/odiff . J'ai également énuméré une liste des modules les plus populaires et expliqué pourquoi ils n'étaient pas acceptables dans le fichier readme de odiff, que vous pouvez consulter si odiff ne possède pas les propriétés souhaitées. Voici un exemple:

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}]}
]
*/
8
B T

J'ai développé la fonction nommée "compareValue ()" en Javascript. il retourne si la valeur est identique ou non. J'ai appelé compareValue () dans la boucle d'un objet. vous pouvez obtenir la différence de deux objets dans diffParams.

var diffParams = {};
var obj1 = {"a":"1", "b":"2", "c":[{"key":"3"}]},
    obj2 = {"a":"1", "b":"66", "c":[{"key":"55"}]};

for( var p in obj1 ){
  if ( !compareValue(obj1[p], obj2[p]) ){
    diffParams[p] = obj1[p];
  }
}

function compareValue(val1, val2){
  var isSame = true;
  for ( var p in val1 ) {

    if (typeof(val1[p]) === "object"){
      var objectValue1 = val1[p],
          objectValue2 = val2[p];
      for( var value in objectValue1 ){
        isSame = compareValue(objectValue1[value], objectValue2[value]);
        if( isSame === false ){
          return false;
        }
      }
    }else{
      if(val1 !== val2){
        isSame = false;
      }
    }
  }
  return isSame;
}
console.log(diffParams);
2
jarangseo

J'ai utilisé ce morceau de code pour effectuer la tâche que vous décrivez:

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

cela vous donnera un nouvel objet qui fusionnera toutes les modifications entre l'ancien et le nouvel objet de votre formulaire

2
AMember

en utilisant lodash.

function difference(object, base) {
    function changes(object, base) {
        return _.transform(object, function(result, value, key) {
            if (!_.isEqual(value, base[key])) {
                result[key] = (_.isObject(value) && _.isObject(base[key])) ? changes(value, base[key]) : value;
            }
        });
    }
    return changes(object, base);
}
2
STEEL

Je sais que je suis en retard à la fête, mais j'avais besoin de quelque chose de similaire pour que les réponses ci-dessus n'aient pas aidé.

J'utilisais la fonction $ watch d'Angular pour détecter les changements dans une variable. Non seulement j'avais besoin de savoir si une propriété avait changé sur la variable, mais je voulais aussi m'assurer que la propriété modifiée n'était pas un champ temporaire calculé. En d'autres termes, je voulais ignorer certaines propriétés.

Voici le code: https://jsfiddle.net/rv01x6jo/

Voici comment l'utiliser:

// 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]);

J'espère que ça aide quelqu'un.

1
a11smiles

Je viens d'utiliser ramda, pour résoudre le même problème, j'ai besoin de savoir ce qui est changé dans le nouvel objet. Alors voici ma conception.

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)

résultat est, nom de la propriété et son statut.

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

J'ai déjà écrit une fonction pour l'un de mes projets qui comparera un objet en tant qu'options utilisateur avec son clone interne. Il peut également valider et même remplacer les valeurs par défaut si l'utilisateur a entré un type de données incorrect ou s'il a été supprimé, en JavaScript pur.

Dans IE8, 100% fonctionne. Testé avec succès.

//  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 ))
//);

/* résultat

{
  "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"
}

*/
0
Samad Aghaei
const diff = require("deep-object-diff").diff;
let differences = diff(obj2, obj1);

Il existe un module npm avec plus de 500 000 téléchargements hebdomadaires: https://www.npmjs.com/package/deep-object-diff

J'aime l’objet comme représentation des différences - en particulier, il est facile de voir la structure, quand elle est formatée.

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

J'ai trébuché ici en essayant de trouver un moyen de faire la différence entre deux objets. Voici ma solution avec 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

Extrait de code ci-dessous:

var last = {
"authed": true,
"inForeground": true,
"goodConnection": false,
"inExecutionMode": false,
"online": true,
"array": [1, 2, 3],
"deep": {
        "nested": "value",
},
"removed": "value",
};

var curr = {
"authed": true,
"inForeground": true,
"deep": {
        "nested": "changed",
},
"array": [1, 2, 4],
"goodConnection": true,
"inExecutionMode": false,
"online": false,
"new": "value"
};

// 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));

console.log('oldValues', JSON.stringify(oldValues));
console.log('updatedValuesIncl', JSON.stringify(updatedValuesIncl));
console.log('updatedValuesExcl', JSON.stringify(updatedValuesExcl));
console.log('newCreatedValues', JSON.stringify(newCreatedValues));
console.log('deletedValues', JSON.stringify(deletedValues));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
0
Jee Mok

La fonction plus étendue et simplifiée de la réponse de sbgoran.
Ceci permet une analyse approfondie et trouve la simillarité d'un tableau.

var result = objectDifference({
      a:'i am unchanged',
      b:'i am deleted',
      e: {a: 1,b:false, c: null},
      f: [1,{a: 'same',b:[{a:'same'},{d: 'delete'}]}],
      g: new Date('2017.11.25'),
      h: [1,2,3,4,5]
  },
  {
      a:'i am unchanged',
      c:'i am created',
      e: {a: '1', b: '', d:'created'},
      f: [{a: 'same',b:[{a:'same'},{c: 'create'}]},1],
      g: new Date('2017.11.25'),
      h: [4,5,6,7,8]
  });
console.log(result);

function objectDifference(obj1, obj2){
    if((dataType(obj1) !== 'array' && dataType(obj1) !== 'object') || (dataType(obj2) !== 'array' && dataType(obj2) !== 'object')){
        var type = '';

        if(obj1 === obj2 || (dataType(obj1) === 'date' && dataType(obj2) === 'date' && obj1.getTime() === obj2.getTime()))
            type = 'unchanged';
        else if(dataType(obj1) === 'undefined')
            type = 'created';
        if(dataType(obj2) === 'undefined')
            type = 'deleted';
        else if(type === '') type = 'updated';

        return {
            type: type,
            data:(obj1 === undefined) ? obj2 : obj1
        };
    }
  
    if(dataType(obj1) === 'array' && dataType(obj2) === 'array'){
        var diff = [];
        obj1.sort(); obj2.sort();
        for(var i = 0; i < obj2.length; i++){
            var type = obj1.indexOf(obj2[i]) === -1?'created':'unchanged';
            if(type === 'created' && (dataType(obj2[i]) === 'array' || dataType(obj2[i]) === 'object')){
                diff.Push(
                    objectDifference(obj1[i], obj2[i])
                );
                continue;
            }
            diff.Push({
                type: type,
                data: obj2[i]
            });
        }

        for(var i = 0; i < obj1.length; i++){
            if(obj2.indexOf(obj1[i]) !== -1 || dataType(obj1[i]) === 'array' || dataType(obj1[i]) === 'object')
                continue;
            diff.Push({
                type: 'deleted',
                data: obj1[i]
            });
        }
    } else {
        var diff = {};
        var key = Object.keys(obj1);
        for(var i = 0; i < key.length; i++){
            var value2 = undefined;
            if(dataType(obj2[key[i]]) !== 'undefined')
                value2 = obj2[key[i]];

            diff[key[i]] = objectDifference(obj1[key[i]], value2);
        }

        var key = Object.keys(obj2);
        for(var i = 0; i < key.length; i++){
            if(dataType(diff[key[i]]) !== 'undefined')
                continue;

            diff[key[i]] = objectDifference(undefined, obj2[key[i]]);
        }
    }

    return diff;
}

function dataType(data){
    if(data === undefined || data === null) return 'undefined';
    if(data.constructor === String) return 'string';
    if(data.constructor === Array) return 'array';
    if(data.constructor === Object) return 'object';
    if(data.constructor === Number) return 'number';
    if(data.constructor === Boolean) return 'boolean';
    if(data.constructor === Function) return 'function';
    if(data.constructor === Date) return 'date';
    if(data.constructor === RegExp) return 'regex';
    return 'unknown';
}
0
StefansArya

Voici une version TypeScript du code @sbgoran

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;

  }
}
0
wzr1337