335 votes

Générique profonde diff entre les deux objets

J'ai deux objets: oldObj et newObj.

Les données en oldObj a été utilisé pour remplir un formulaire et l' newObj est le résultat de l'utilisateur modifie les données de ce formulaire et de le soumettre.

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 être n niveaux de profondeur, donc l'algorithme de comparaison doit être récursive.

Maintenant, j'ai besoin non pas seulement de comprendre ce qui a changé (comme dans ajout/mise à jour/suppression) d' oldObj de newObj, mais aussi à la meilleure manière de le représenter.

Pour l'instant, mes pensées était de construire un genericDeepDiffBetweenObjects méthode qui retourne un objet de la forme {add:{...},upd:{...},del:{...}} mais ensuite j'ai pensé: quelqu'un d'autre doit avoir besoin de cette avant.

Alors... personne ne sait d'une bibliothèque ou d'un morceau de code qui va le faire et peut-être avoir une meilleure façon de représenter la différence (qui est encore JSON serializable)?

Mise à jour:

J'ai pensé une meilleure façon de représenter les données mises à jour, en utilisant la même structure de l'objet en tant que newObj, mais de tourner toutes les valeurs de propriété dans les objets sur le formulaire:

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

Donc, si newObj.prop1 = 'new value' et oldObj.prop1 = 'old value' ce serait, returnObj.prop1 = {type: 'update', data: 'new value'}

Mise à jour 2:

Il devient vraiment poilue quand nous arrivons à des propriétés qui sont des tableaux, depuis le tableau [1,2,3] devraient être considérés comme des égaux [2,3,1], ce qui est assez simple pour les tableaux de la base de la valeur de type string, int & bool, mais devient vraiment difficile à gérer quand il s'agit de tableaux de référence des types comme les objets et les tableaux.

Exemple de tableaux qui devrait être trouvé à l'égalité:

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

Non seulement, c'est assez complexe à vérifier pour ce type de valeur profonde de l'égalité, mais aussi de trouver une bonne façon de représenter les modifications qui pourraient être.

214voto

sbgoran Points 961

J'ai écrit un peu de classe qui est en train de faire ce que vous voulez, vous pouvez le tester ici.

Seule chose qui est différent de votre proposition, c'est que je ne considère pas l' [1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]] à être le même, parce que je pense que les tableaux ne sont pas égaux si l'ordre de leurs éléments n'est pas la même. Bien sûr, cela peut être changé si nécessaire. Aussi ce code peut être encore amélioré pour prendre fonction en tant qu'argument qui sera utilisé pour le format diff objet dans l'arbitraire en se basant sur le passé des valeurs primitives (maintenant, ce travail est fait par "compareValues" la méthode).

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

            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;

        },
        compareValues: function(value1, value2) {
            if (value1 === value2) {
                return this.VALUE_UNCHANGED;
            }
            if ('undefined' == typeof(value1)) {
                return this.VALUE_CREATED;
            }
            if ('undefined' == typeof(value2)) {
                return this.VALUE_DELETED;
            }

            return this.VALUE_UPDATED;
        },
        isFunction: function(obj) {
            return {}.toString.apply(obj) === '[object Function]';
        },
        isArray: function(obj) {
            return {}.toString.apply(obj) === '[object Array]';
        },
        isObject: function(obj) {
            return {}.toString.apply(obj) === '[object Object]';
        },
        isValue: function(obj) {
            return !this.isObject(obj) && !this.isArray(obj);
        }
    }
}();


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'}]}]
  },
  {
      a:'i am unchanged',
      c:'i am created',
      e:{ a: '1', b: '', d:'created'},
      f: [{a: 'same',b:[{a:'same'},{c: 'create'}]},1]

  });
console.log(result);

116voto

drzaus Points 3344

À l'aide de Souligner, une 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 pièces de o1 qui correspondent mais avec des valeurs différentes en o2:

{a: 1, b: 2}

Ce serait différent pour une profonde 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;
}

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

11voto

Nikita Koksharov Points 1552

Voici une solution toute prête pour cela: https://github.com/NV/objectDiff.js

5voto

Benja Points 667

voici une très sophistiqué de la bibliothèque de diff/patch toute paire d'objets Javascript

https://github.com/benjamine/jsondiffpatch

vous pouvez voir en direct: http://benjamine.github.io/jsondiffpatch/demo/index.html

(disclaimer: je suis l'auteur)

1voto

AMember Points 1664

J'ai utilisé ce bout de code pour faire 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;
}

ainsi, vous obtenez un nouvel objet qui va fusionner tous les changements entre l'objet ancien et le nouvel objet à partir de votre formulaire

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X