两个对象之间的generics深度差异
我有两个对象: oldObj
和newObj
。
oldObj
中的数据用于填充表单, newObj
是用户更改此表单中的数据并提交它的结果。
两个对象都很深,即。 它们的属性是对象或对象数组等等 – 它们可以是n级深度的,因此diffalgorithm需要recursion。
现在我不仅需要oldObj
到newObj
变化情况(如添加/更新/删除),还要了解如何更好地表示它。
到目前为止,我的想法是build立一个genericDeepDiffBetweenObjects
方法,该方法将返回格式为{add:{...},upd:{...},del:{...}}
但后来我想:否则之前一定需要这个。
所以…有没有人知道一个库或一段代码将做到这一点,也许有一个更好的方式来表示不同(以仍然是JSON序列化的方式)?
更新:
我想通过使用与newObj
相同的对象结构来更好地表示更新的数据,但将所有属性值转换为表单上的对象:
{type: '<update|create|delete>', data: <propertyValue>}
所以如果newObj.prop1 = 'new value'
和oldObj.prop1 = 'old value'
它会设置returnObj.prop1 = {type: 'update', data: 'new value'}
更新2:
由于数组[1,2,3]
应该被计算为等于[2,3,1]
,这对于基于值的数组(如string)来说足够简单, int&bool,但是当涉及像对象和数组这样的引用types的数组时,真的很难处理。
应该find相同的示例数组:
[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]
检查这种深刻的价值平等不仅是相当复杂的,而且也是想出一个很好的方式来表示可能发生的变化。
我写了一个正在做你想做的事的小class,你可以在这里testing一下 。
唯一不同于你的build议的是,我不认为[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]
是相同的,因为我认为如果它们的元素的顺序不相同,那么数组是不相等的。 当然这可以根据需要进行更改。 此外,这段代码可以进一步增强,以函数作为参数,用于根据传递的原始值以任意方式格式化diff对象(现在这个工作由“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);
使用Underscore,一个简单的差异:
var o1 = {a: 1, b: 2, c: 2}, o2 = {a: 2, b: 1, c: 2}; _.omit(o1, function(v,k) { return o2[k] === v; })
结果在o1
相应部分,但在o2
有不同的值:
{a: 1, b: 2}
对于深刻的差异,这将是不同的:
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; }
正如@Juhana在评论中指出的那样,以上只是diff a – > b而不是可逆的 (意味着b中的额外属性将被忽略)。 用来代替 – > 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());
这里有一个非常复杂的库来区分/修补任何一对Javascript对象
https://github.com/benjamine/jsondiffpatch
你可以在这里看到它: http : //benjamine.github.io/jsondiffpatch/demo/index.html
(免责声明:我是作者)
使用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); } });
我想提供一个ES6解决scheme…这是一个单向差异,这意味着它将从o2
返回与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] } }, {})
下面是一个JavaScript库,您可以使用它来查找两个JavaScript对象之间的差异:
Githuburl: https : //github.com/cosmicanant/recursive-diff
Npmjsurl: https ://www.npmjs.com/package/recursive-diff
您可以在浏览器中使用recursive-diff库以及Node.js. 对于浏览器,请执行以下操作:
<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>
而在node.js中,您可以要求'recursion – 差异'模块并使用它,如下所示:
var diff = require('recursive-diff'); var ob1 = {a: 1}, ob2: {b:2}; var diff = diff.getDiff(ob1, ob2);
致力于解决这个问题的另一个项目可以在https://github.com/flitbit/difffind。;
这几天,有相当多的模块可用于此。 我最近写了一个模块来做到这一点,因为我不满意我发现的众多差异模块。 它叫做odiff
: https : //github.com/Tixit/odiff 。 我还列出了一些最受欢迎的模块,以及为什么他们在odiff
的自述文件中odiff
,如果odiff
没有你想要的属性,你可以查看一下。 这是一个例子:
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}]} ] */
我用这段代码来完成你所描述的任务:
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; }
这将会得到一个新的对象,它将合并表单中的旧对象和新对象之间的所有变化
我在Javascript中开发了名为“compareValue()”的函数。 它会返回值是否相同。 我在一个对象的循环中调用了compareValue()。 你可以在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);
我已经为我的一个项目编写了一个函数,将一个对象作为用户选项与其内部的克隆进行比较。 如果用户input了错误的数据types或者被删除了,那么它也可以validation甚至replace为默认值。
在IE8 100%的作品。 testing成功。
// 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(/^[az][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 )) //);
/ *结果
{ "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" } */
为什么不在整个arrays上做一个可重复的可预测sorting?
如果顺序不相关(正如你所说),那么一个相当不错的数组sorting会给你可比较的数组…
嘿,你甚至可以创build一个两个数组的散列,并比较它们,看看如果CREATE UPDATE OR DELETE甚至是相关的,然后再下降arrays和节点比较节点…
一旦他们被sorting,你可以加快一个string比较呢?
我知道我迟到了,但我需要类似的东西,上面的答案没有帮助。
我正在使用Angular的$ watch函数来检测variables的变化。 我不仅需要知道房地产是否在variables上发生了变化,而且还想确保变化的房产不是一个临时的计算区域。 换句话说,我想忽略某些属性。
这是代码: https : //jsfiddle.net/rv01x6jo/
以下是如何使用它:
// 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]);
希望这有助于某人。