2008-11-05 13 views
16

Quiero ser capaz de obtener una lista de todas las diferencias entre dos gráficos de objetos de JavaScript, con los nombres de propiedad y valores donde ocurren los deltas.¿Cómo puedo obtener una lista de las diferencias entre dos gráficos de objetos de JavaScript?

Por lo que vale, estos objetos normalmente se recuperan del servidor como JSON y normalmente no son más que un puñado de capas de profundidad (es decir, puede ser una matriz de objetos que tienen datos y luego matrices con otros datos objetos).

Quiero ver no sólo los cambios en las propiedades básicas, pero las diferencias en el número de miembros de una matriz, etc., etc.

Si no consigo una respuesta, que probablemente terminará la escritura esto mismo, pero espero que alguien ya haya hecho este trabajo o conozca a alguien que sí lo haya hecho.


EDIT: Estos objetos serán típicamente muy cerca en estructura a la otra, por lo que no estamos hablando acerca de los objetos que son completamente diferentes entre sí, pero pueden tener 3 o 4 deltas.

+0

Jason, gracias por su apoyo. Sin embargo, voy a eliminar mi respuesta. Obviamente, no es lo que buscabas después, y la implementación es discutible, así que supongo que no tiene sentido mantenerlo. Si tiene una solución que se adapte a sus necesidades, estaría interesado en verla aquí. – Tomalak

+0

Estoy decepcionado de que haya decidido eliminarlo, pero respete su derecho a hacerlo. Simplemente he aprendido que hay cosas que se pueden aprender de las soluciones de los demás a los problemas, incluso si no resuelven el problema exacto para el que fueron diseñados. Podemos aprender de casi cualquier cosa. –

Respuesta

12

Después de revisar las respuestas existentes, me di cuenta de que la biblioteca https://github.com/flitbit/diff aún no fue catalogado como una solución.

Según mi investigación, esta biblioteca parece ser la mejor en términos de desarrollo activo, contribuciones y tenedores para resolver el desafío de diferir objetos. Esto es muy útil para crear un diff en el lado del servidor y pasar al cliente solo los bits cambiados.

+1

Cambié mi respuesta oficial de la mía, a la tuya, ya que esta es una pregunta tan antigua, ¡y el mundo de JavaScript ha cambiado significativamente desde que lo escribí hace 7 años! :) –

+1

Sí, el mundo de JS ha cambiado enormemente. ¡Nunca pensé que estaría programando un back-end en JavaScript! Gracias, @JasonBunting – ryanm

19

Aquí hay una solución parcial e ingenua a mi problema. Lo actualizaré a medida que lo desarrolle.

function findDifferences(objectA, objectB) { 
    var propertyChanges = []; 
    var objectGraphPath = ["this"]; 
    (function(a, b) { 
     if(a.constructor == Array) { 
     // BIG assumptions here: That both arrays are same length, that 
     // the members of those arrays are _essentially_ the same, and 
     // that those array members are in the same order... 
     for(var i = 0; i < a.length; i++) { 
      objectGraphPath.push("[" + i.toString() + "]"); 
      arguments.callee(a[i], b[i]); 
      objectGraphPath.pop(); 
     } 
     } else if(a.constructor == Object || (a.constructor != Number && 
       a.constructor != String && a.constructor != Date && 
       a.constructor != RegExp && a.constructor != Function && 
       a.constructor != Boolean)) { 
     // we can safely assume that the objects have the 
     // same property lists, else why compare them? 
     for(var property in a) { 
      objectGraphPath.push(("." + property)); 
      if(a[property].constructor != Function) { 
       arguments.callee(a[property], b[property]); 
      } 
      objectGraphPath.pop(); 
     } 
     } else if(a.constructor != Function) { // filter out functions 
     if(a != b) { 
      propertyChanges.push({ "Property": objectGraphPath.join(""), "ObjectA": a, "ObjectB": b }); 
     } 
     } 
    })(objectA, objectB); 
    return propertyChanges; 
} 

Y aquí es una muestra de cómo se puede utilizar y los datos que proporcionaría (disculpen el largo ejemplo, pero quiero usar algo relativamente no trivial):

var person1 = { 
    FirstName : "John", 
    LastName : "Doh", 
    Age : 30, 
    EMailAddresses : [ 
     "[email protected]", 
     "[email protected]" 
    ], 
    Children : [ 
     { 
     FirstName : "Sara", 
     LastName : "Doe", 
     Age : 2 
     }, { 
     FirstName : "Beth", 
     LastName : "Doe", 
     Age : 5 
     } 
    ] 
}; 

var person2 = { 
    FirstName : "John", 
    LastName : "Doe", 
    Age : 33, 
    EMailAddresses : [ 
     "[email protected]", 
     "[email protected]" 
    ], 
    Children : [ 
     { 
     FirstName : "Sara", 
     LastName : "Doe", 
     Age : 3 
     }, { 
     FirstName : "Bethany", 
     LastName : "Doe", 
     Age : 5 
     } 
    ] 
}; 

var differences = findDifferences(person1, person2); 

en este punto, esto es lo que la matriz differences se vería como si serializado a JSON:

[ 
    { 
     "Property":"this.LastName", 
     "ObjectA":"Doh", 
     "ObjectB":"Doe" 
    }, { 
     "Property":"this.Age", 
     "ObjectA":30, 
     "ObjectB":33 
    }, { 
     "Property":"this.EMailAddresses[1]", 
     "ObjectA":"[email protected]", 
     "ObjectB":"[email protected]" 
    }, { 
     "Property":"this.Children[0].Age", 
     "ObjectA":2, 
     "ObjectB":3 
    }, { 
     "Property":"this.Children[1].FirstName", 
     "ObjectA":"Beth", 
     "ObjectB":"Bethany" 
    } 
] 

el this en el Property valor se refiere a la raíz del objeto que se comparó. Por lo tanto, esta solución aún no es exactamente lo que necesito, pero está bastante cerca.

Espero que esto sea útil para alguien, y si tiene alguna sugerencia para mejorar, soy todo oídos; Escribí esto muy tarde anoche (es decir, temprano esta mañana) y puede haber cosas que estoy pasando por alto por completo.

Gracias.

+0

Esto funciona de maravilla gracias, aunque quiero construir un objeto de diferencias –

+2

Chicos, por favor presten atención a eso: en ES5 arguments.callee está prohibido en modo estricto. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/callee – BotanMan

4

Solución 1

Uso JSON.stringify (obj) para obtener una representación de cadena de los objetos que desea comparar. Guarde la cadena en un archivo. Use cualquier visor de diferencias para comparar los archivos de texto.

Nota: JSON.stringify ignorará las propiedades que apuntan a las definiciones de funciones.

Solución 2

Esto podría hacer lo que quiera con alguna modificación, que es una versión modificada de la función _.isEqual (http://documentcloud.github.com/underscore/). Por favor, siéntase libre de sugerir cualquier modificación! Lo escribí para descubrir dónde ocurre la primera diferencia entre dos objetos.

// Given two objects find the first key or value not matching, algorithm is a 
// inspired by of _.isEqual. 
function diffObjects(a, b) { 
    console.info("---> diffObjects", {"a": a, "b": b}); 
    // Check object identity. 
    if (a === b) return true; 
    // Different types? 
    var atype = typeof(a), btype = typeof(b); 
    if (atype != btype) { 
    console.info("Type mismatch:", {"a": a, "b": b}); 
    return false; 
    }; 
    // Basic equality test (watch out for coercions). 
    if (a == b) return true; 
    // One is falsy and the other truthy. 
    if ((!a && b) || (a && !b)) { 
    console.info("One is falsy and the other truthy:", {"a": a, "b": b}); 
    return false; 
    } 
    // Unwrap any wrapped objects. 
    if (a._chain) a = a._wrapped; 
    if (b._chain) b = b._wrapped; 
    // One of them implements an isEqual()? 
    if (a.isEqual) return a.isEqual(b); 
    // Check dates' integer values. 
    if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); 
    // Both are NaN? 
    if (_.isNaN(a) && _.isNaN(b)) { 
    console.info("Both are NaN?:", {"a": a, "b": b}); 
    return false; 
    } 
    // Compare regular expressions. 
    if (_.isRegExp(a) && _.isRegExp(b)) 
    return a.source  === b.source && 
      a.global  === b.global && 
      a.ignoreCase === b.ignoreCase && 
      a.multiline === b.multiline; 
    // If a is not an object by this point, we can't handle it. 
    if (atype !== 'object') { 
    console.info("a is not an object:", {"a": a}); 
    return false; 
    } 
    // Check for different array lengths before comparing contents. 
    if (a.length && (a.length !== b.length)) { 
    console.info("Arrays are of different length:", {"a": a, "b": b}); 
    return false; 
    } 
    // Nothing else worked, deep compare the contents. 
    var aKeys = _.keys(a), bKeys = _.keys(b); 
    // Different object sizes? 
    if (aKeys.length != bKeys.length) { 
    console.info("Different object sizes:", {"a": a, "b": b}); 
    return false; 
    } 
    // Recursive comparison of contents. 
    for (var key in a) if (!(key in b) || !diffObjects(a[key], b[key])) return false; 
    return true; 
}; 
5

También puede probar rus-diff https://github.com/mirek/node-rus-diff que genera la diferencia compatible con MongoDB (rename/unset/set).

Para su ejemplo objetos:

var person1 = { 
    FirstName: "John", 
    LastName: "Doh", 
    Age: 30, 
    EMailAddresses: ["[email protected]", "[email protected]"], 
    Children: [ 
    { 
     FirstName: "Sara", 
     LastName: "Doe", 
     Age: 2 
    }, { 
     FirstName: "Beth", 
     LastName: "Doe", 
     Age: 5 
    } 
    ] 
}; 

var person2 = { 
    FirstName: "John", 
    LastName: "Doe", 
    Age: 33, 
    EMailAddresses: ["[email protected]", "[email protected]"], 
    Children: [ 
    { 
     FirstName: "Sara", 
     LastName: "Doe", 
     Age: 3 
    }, { 
     FirstName: "Bethany", 
     LastName: "Doe", 
     Age: 5 
    } 
    ] 
}; 

var rusDiff = require('rus-diff').rusDiff 

console.log(rusDiff(person1, person2)) 

Se genera una lista de conjuntos:

{ '$set': 
    { 'Age': 33, 
    'Children.0.Age': 3, 
    'Children.1.FirstName': 'Bethany', 
    'EMailAddresses.1': '[email protected]', 
    'LastName': 'Doe' } } 
1

Hace poco escribí un módulo para hacer esto, porque no estaba satisfecho con los numerosos módulos diffing Encontré (enumeré un montón de los módulos más populares y por qué no eran aceptables en el archivo léame de mi módulo). Se llama odiff: https://github.com/Tixit/odiff. He aquí un ejemplo:

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}]} 
] 
*/ 
0

Ninguna de las bibliotecas que encontré fueron suficientes, así que escribí mi propia fábrica AngularJS. Compara los objetos de ambas maneras y solo devuelve la diferencia dentro de la misma estructura.

/** 
* Diff 
* Original author: Danny Coulombe 
* Creation date: 2016-05-18 
* 
* Work with objects to find their differences. 
*/ 
controllers.factory('diff', [function() { 

    var factory = { 

     /** 
     * Compare the original object with the modified one and return their differences. 
     * 
     * @param original: Object 
     * @param modified: Object 
     * 
     * @return Object 
     */ 
     getDifferences: function(original, modified) { 

      var type = modified.constructor === Array ? [] : {}; 
      var result = angular.copy(type); 
      var comparisons = [[original, modified, 1], [modified, original, 0]]; 

      comparisons.forEach(function(comparison) { 

       angular.forEach(comparison[0], function(value, key) { 

        if(result[key] === undefined) { 

         if(comparison[1][key] !== undefined && value !== null && comparison[1][key] !== null && [Object, Array].indexOf(comparison[1][key].constructor) !== -1) { 

          result[key] = factory.getDifferences(value, comparison[1][key]); 
         } 
         else if(comparison[1][key] !== value) { 

          result[key] = comparison[comparison[2]][key]; 
         } 

         if(angular.equals(type, result[key]) 
         || result[key] === undefined 
         || (
          comparison[0][key] !== undefined 
          && result[key] !== null 
          && comparison[0][key] !== null 
          && comparison[0][key].length === comparison[1][key].length 
          && result[key].length === 0 
         )) { 
          delete result[key]; 
         } 
        } 
       }); 
      }); 

      return result; 
     } 
    }; 

    return factory; 
}]); 
+0

obtengo "la fuente no está definida" cuando ejecuto esto. ¿Se supone que "original" es "fuente"? Además, ¿dónde se define "fromObjects"? – adam0101

+0

Lo siento, cambié algunos nombres de variables al escribir esta respuesta. "fuente" fue "original" y "de Objetos" fue "obtenerDifferencias". Actualicé el script arriba. –

0

var d = { 
 
    FirstName : "John", 
 
    LastName : "Doh", 
 
    Age : 30, 
 
    EMailAddresses : [ 
 
     "[email protected]", 
 
     "[email protected]" 
 
    ], 
 
    Children : [ 
 
     { 
 
     FirstName : "Sara", 
 
     LastName : "Doe", 
 
     Age : 2 
 
     }, { 
 
     FirstName : "Beth", 
 
     LastName : "Doe", 
 
     Age : 5 
 
     } 
 
    ] 
 
}; 
 

 
var f = { 
 
    FirstName : "John", 
 
    LastName : "Doe", 
 
    Age : 33, 
 
    EMailAddresses : [ 
 
     "[email protected]", 
 
     "[email protected]" 
 
    ], 
 
    Children : [ 
 
     { 
 
     FirstName : "Sara", 
 
     LastName : "Doe", 
 
     Age : 3 
 
     }, { 
 
     FirstName : "Bethany", 
 
     LastName : "Doe", 
 
     Age : 5 
 
     } 
 
    ] 
 
}; 
 

 
resultobj = [] 
 
function comp_obj(t1,t2){ 
 
\t flag = 1; 
 
\t key1_arr = Object.keys(t1) 
 
\t key2_arr = Object.keys(t2) 
 
\t if(key1_arr.length == key2_arr.length){ 
 
\t \t for(key1 in t1){ 
 
\t \t \t ty1 = Object.prototype.toString.call(t1[key1]) 
 
\t \t \t ty2 = Object.prototype.toString.call(t2[key1]) 
 
\t \t \t if(ty1 == ty2) { 
 
\t \t \t \t if(ty1 == '[object String]' || ty1 == '[object Number]'){ 
 
\t \t \t \t \t if(t2[key1] != t1[key1]){ 
 
\t \t \t \t \t \t flag = 0; 
 
\t \t \t \t \t \t break; 
 
\t \t \t \t \t } \t 
 
\t \t \t \t }else if(ty1 == '[object Array]'){ 
 
\t \t \t \t \t var result = comp_arr(t1[key1],t2[key1]); 
 
\t \t \t \t \t console.log(ty1,ty2) 
 
\t \t \t \t \t if(!result) 
 
\t \t \t \t \t \t flag = 0; 
 
\t \t \t \t }else if(ty1 == '[object Object]'){ 
 
\t \t \t \t \t var result = comp_obj(t1[key1],t2[key1]) 
 
\t \t \t \t \t if(!result) 
 
\t \t \t \t \t \t flag = 0; 
 
\t \t \t \t \t \t 
 
\t \t \t \t } 
 
\t \t \t }else{ 
 
\t \t \t \t flag = 0; 
 
\t \t \t \t break; \t 
 
\t \t \t } 
 

 
\t \t } 
 
\t }else{ 
 
\t \t flag \t = 0; 
 
\t } 
 
\t if(flag) 
 
\t \t return true 
 
\t else 
 
\t \t return false; 
 
} 
 
function comp_arr(a,b){ 
 
\t flag = 1; 
 
\t if(a.length == b.length){ 
 
\t \t for(var i=0,l=a.length;i<l;i++){ 
 
\t \t \t type1 = Object.prototype.toString.call(a[i]) 
 
\t \t \t type2 = Object.prototype.toString.call(b[i]) 
 
\t \t \t if(type1 == type2) { 
 
\t \t \t \t if(type1 == '[object String]' || type1 == '[object Number]'){ 
 
\t \t \t \t \t if(a[i] != b[i]){ 
 
\t \t \t \t \t \t flag = 0; 
 
\t \t \t \t \t \t break; 
 
\t \t \t \t \t } \t 
 
\t \t \t \t }else if(type1 == '[object Array]'){ 
 
\t \t \t \t \t var result = comp_arr(a[i],b[i]); 
 
\t \t \t \t \t if(!result) 
 
\t \t \t \t \t \t flag = 0; 
 
\t \t \t \t }else if(type1 == '[object Object]'){ 
 
\t \t \t \t \t var result = comp_obj(a[i],b[i]) 
 
\t \t \t \t \t if(!result) 
 
\t \t \t \t \t \t flag = 0; 
 
\t \t \t \t } \t 
 
\t \t \t }else{ 
 
\t \t \t \t flag = 0; 
 
\t \t \t \t break; \t 
 
\t \t \t } 
 
\t \t } 
 
\t }else 
 
\t \t flag = 0; 
 
\t if(flag) 
 
\t \t return true 
 
\t else 
 
\t \t return false; 
 
} 
 
function create(t,attr,parent_node,innerdata){ 
 
\t var dom = document.createElement(t) 
 
\t for(key in attr){ 
 
\t \t dom.setAttribute(key,attr[key]) 
 
\t } 
 
\t dom.innerHTML = innerdata; 
 
\t parent_node.appendChild(dom) 
 
\t return dom; 
 
} 
 
window.onload = function() { 
 
\t for(key in f){ 
 
\t \t if(!(key in d)) 
 
\t \t \t resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])}) 
 
\t \t type1 = Object.prototype.toString.call(f[key]) 
 
\t \t type2 = Object.prototype.toString.call(d[key]) 
 
\t \t if(type1 == type2){ 
 
\t \t \t if(type1 == '[object String]' || type1 == '[object Number]'){ 
 
\t \t \t \t if(f[key] != d[key]) 
 
\t \t \t \t \t resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])}) 
 
\t \t \t }else if(type1 == '[object Array]'){ 
 
\t \t \t \t var result = comp_arr(f[key],d[key]); 
 
\t \t \t \t if(!result) 
 
\t \t \t \t \t resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])}) 
 
\t \t \t }else if(type1 == '[object Object]'){ 
 
\t \t \t \t var result = comp_obj(f[key],d[key]) \t 
 
\t \t \t \t if(!result) 
 
\t \t \t \t \t resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])}) 
 
\t \t \t } 
 
\t \t }else 
 
\t \t \t resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])}) 
 
\t } 
 
\t var tb = document.getElementById('diff'); 
 
\t var s1 = document.getElementById('source1'); 
 
\t var s2 = document.getElementById('source2'); 
 
\t s1.innerHTML = 'Object 1 :'+ JSON.stringify(f) 
 
\t s2.innerHTML = 'Object 2 :'+JSON.stringify(d) 
 
\t resultobj.forEach(function(data,i){ 
 
\t \t \t tr_dom = create('tr',{},tb,'') 
 
\t \t \t no = create('td',{},tr_dom,i+1) 
 
\t \t \t Object.keys(data).forEach(function(tr){ 
 
\t \t \t \t td_dom = create('td',{},tr_dom,data[tr]) 
 
\t \t \t }) 
 
\t }) 
 
}
<html> 
 
     <body> 
 
       <p id="source1"> </p> 
 
       <p id="source2"> </p> 
 
       <p id="source7"> DIFFERENCE TABLE</p> 
 
       <table border=''> 
 
         <thead> 
 
           <th>S.no</th> 
 
           <th>Name Of the Key</th> 
 
           <th>Object1 Value</th> 
 
           <th>Object2 Value</th> 
 
         </thead> 
 
         <tbody id="diff"> 
 

 
         </tbody> 
 
       </table> 
 
      </body> 
 
</html>

Cuestiones relacionadas