w3schools tag tab style color javascript

javascript - tag - title html w3schools



Diferencia profunda genérica entre dos objetos (12)

Tengo dos objetos: oldObj y newObj .

Los datos en oldObj se usaron para rellenar un formulario y newObj es el resultado de que el usuario cambia los datos en este formulario y lo envía.

Ambos objetos son profundos, es decir. tienen propiedades que son objetos o matrices de objetos, etc., pueden tener n niveles profundos, por lo que el algoritmo diff debe ser recursivo.

Ahora necesito no solo averiguar qué fue cambiado (como en agregado / actualizado / eliminado) de oldObj a newObj , sino también cómo representarlo mejor.

Hasta ahora, mis pensamientos eran solo crear un método genericDeepDiffBetweenObjects que devolviera un objeto en el formulario {add:{...},upd:{...},del:{...}} pero luego pensé: alguien de lo contrario debe haber necesitado esto antes.

Entonces ... ¿alguien sabe de una biblioteca o un código que lo haga y tal vez tenga una mejor manera de representar la diferencia (de una manera que todavía sea serializable por JSON)?

Actualizar:

He pensado en una mejor forma de representar los datos actualizados, utilizando la misma estructura de objetos que newObj , pero convirtiendo todos los valores de propiedad en objetos en el formulario:

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

Entonces, si newObj.prop1 = ''new value'' y oldObj.prop1 = ''old value'' establecería returnObj.prop1 = {type: ''update'', data: ''new value''}

Actualización 2:

Se pone realmente peludo cuando llegamos a las propiedades que son matrices, ya que la matriz [1,2,3] debe contar como igual a [2,3,1] , que es lo suficientemente simple para matrices de tipos basados ​​en valores como cadena, int & bool, pero se vuelve realmente difícil de manejar cuando se trata de matrices de tipos de referencia como objetos y matrices.

Ejemplo de matrices que se deben encontrar iguales:

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

No solo es bastante complejo comprobar este tipo de igualdad de valores profundos, sino también descubrir una buena forma de representar los cambios que podrían ser.


Aquí hay una biblioteca de JavaScript que puede usar para encontrar diferencias entre dos objetos de JavaScript:

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

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

Puede usar la biblioteca recursive-diff en el navegador y en Node.js. Para el navegador, haga lo siguiente:

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

Mientras que en node.js puede requerir el módulo ''recursive-diff'' y usarlo como se muestra a continuación:

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


Desarrollé la función llamada "compareValue ()" en Javascript. devuelve si el valor es igual o no. He llamado compareValue () en el ciclo de un Objeto. puedes obtener la diferencia de dos objetos en 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);


En estos días, hay bastantes módulos disponibles para esto. Hace poco escribí un módulo para hacer esto, porque no estaba satisfecho con los numerosos módulos diferentes que encontré. Se llama odiff : https://github.com/Tixit/odiff . También enumeré un montón de los módulos más populares y por qué no eran aceptables en el odiff de odiff , que podría echarle un vistazo si odiff no tiene las propiedades que desea. Aquí hay 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}]} ] */


Escribí una pequeña clase que está haciendo lo que quieres, puedes probarla here .

Lo único que es diferente de su propuesta es que no considero [1,[{c: 1},2,3],{a:''hey''}] and [{a:''hey''},1,[3,{c: 1},2]] para que sean iguales, porque creo que las matrices no son iguales si el orden de sus elementos no es el mismo. Por supuesto, esto puede cambiarse si es necesario. También este código se puede mejorar aún más para tomar la función como argumento que se utilizará para formatear el objeto diff de forma arbitraria en base a los valores primitivos pasados ​​(ahora este trabajo se realiza mediante el método "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 (''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 (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) { 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]''; }, isDate: function(obj) { return {}.toString.apply(obj) === ''[object Date]''; }, 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''}]}], 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);


He usado este fragmento de código para hacer la tarea que describes:

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

esto le dará un nuevo objeto que fusionará todos los cambios entre el objeto antiguo y el nuevo objeto de su formulario


La función más extendida y simplificada de la respuesta de sgogoran.
Esto permite una exploración profunda y encuentra la similitud de una matriz.

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


Me gustaría ofrecer una solución ES6 ... Esta es una diferencia unidireccional, lo que significa que devolverá claves / valores de o2 que no son idénticos a sus contrapartes en 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] } }, {})


Sé que llego tarde a la fiesta, pero necesitaba algo similar que las respuestas anteriores no ayudaron.

Estaba usando la función $ watch de Angular para detectar cambios en una variable. No solo necesitaba saber si una propiedad había cambiado en la variable, sino que también quería asegurarme de que la propiedad que cambió no era un campo temporal y calculado. En otras palabras, quería ignorar ciertas propiedades.

Aquí está el código: https://jsfiddle.net/rv01x6jo/

He aquí cómo usarlo:

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

Espero que esto ayude a alguien.


Solo uso ramda, para resolver el mismo problema, necesito saber qué cambia en el nuevo objeto. Entonces aquí mi diseño.

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)

el resultado es, nombre de la propiedad y su estado.

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


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

No uso key / object / source pero lo dejé allí si necesita acceder a ellos. La comparación de objetos solo evita que la consola imprima las diferencias a la consola desde el elemento más externo al elemento más interno.

Puede agregar algo de lógica dentro para manejar matrices. Quizás clasifique las matrices primero. Esta es una solución muy flexible.

EDITAR

Cambió de _.merge a _.mergeWith debido a la actualización de lodash. Gracias Aviron por notar el cambio.


Usando Underscore, una 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; })

Resultados en las partes de o1 que corresponden pero con diferentes valores en o2 :

{a: 1, b: 2}

Sería diferente para una diferencia profunda:

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

Como señala @Juhana en los comentarios, lo anterior es solo un diff a -> b y no reversible (es decir, las propiedades adicionales en b se ignorarían). Use en su lugar 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());

Consulte http://jsfiddle.net/drzaus/9g5qoxwj/ para obtener un ejemplo completo + pruebas + mixins


Ya escribí una función para uno de mis proyectos que comparará un objeto como opciones de usuario con su clon interno. También puede validar e incluso reemplazar por valores predeterminados si el usuario ingresó datos de tipo incorrectos o eliminados, en javascript puro.

En IE8 100% funciona. Probado con éxito.

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

/ * resultado

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