Javascript 两个对象之间的通用深度差异
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8572826/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Generic deep diff between two objects
提问by Martin Jespersen
I have two objects: oldObj
and newObj
.
我有两个对象:oldObj
和newObj
。
The data in oldObj
was used to populate a form and newObj
is the result of the user changing data in this form and submitting it.
输入的数据oldObj
用于填充表单,newObj
是用户更改此表单中的数据并提交的结果。
Both objects are deep, ie. they have properties that are objects or arrays of objects etc - they can be n levels deep, thus the diff algorithm needs to be recursive.
两个对象都很深,即。它们具有对象或对象数组等属性 - 它们可以是 n 级深,因此 diff 算法需要递归。
Now I need to not just figure out what was changed (as in added/updated/deleted) from oldObj
to newObj
, but also how to best represent it.
现在我需要不只是从弄清楚什么改变(如添加/更新/删除)oldObj
来newObj
,却怎么也最能代表它。
So far my thoughts was to just build a genericDeepDiffBetweenObjects
method that would return an object on the form {add:{...},upd:{...},del:{...}}
but then I thought: somebody else must have needed this before.
到目前为止,我的想法只是构建一个genericDeepDiffBetweenObjects
方法来返回表单上的对象,{add:{...},upd:{...},del:{...}}
但后来我想:之前一定有人需要这个。
So... does anyone know of a library or a piece of code that will do this and maybe have an even better way of representing the difference (in a way that is still JSON serializable)?
那么......有没有人知道一个库或一段代码可以做到这一点,并且可能有更好的方式来表示差异(以仍然是 JSON 可序列化的方式)?
Update:
更新:
I have thought of a better way to represent the updated data, by using the same object structure as newObj
, but turning all property values into objects on the form:
我想到了一种更好的方法来表示更新的数据,使用与 相同的对象结构newObj
,但将所有属性值转换为表单上的对象:
{type: '<update|create|delete>', data: <propertyValue>}
So if newObj.prop1 = 'new value'
and oldObj.prop1 = 'old value'
it would set returnObj.prop1 = {type: 'update', data: 'new value'}
所以,如果newObj.prop1 = 'new value'
和oldObj.prop1 = 'old value'
它会设置returnObj.prop1 = {type: 'update', data: 'new value'}
Update 2:
更新 2:
It gets truely hairy when we get to properties that are arrays, since the array [1,2,3]
should be counted as equal to [2,3,1]
, which is simple enough for arrays of value based types like string, int & bool, but gets really difficult to handle when it comes to arrays of reference types like objects and arrays.
当我们处理作为数组的属性时,它真的很麻烦,因为数组[1,2,3]
应该被计算为等于[2,3,1]
,这对于基于值的类型(如 string、int 和 bool)的数组来说足够简单,但是当涉及到时就变得非常难以处理引用类型的数组,如对象和数组。
Example arrays that should be found equal:
应该发现相等的示例数组:
[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]
Not only is it quite complex to check for this type of deep value equality, but also to figure out a good way to represent the changes that might be.
检查这种类型的深度值相等性不仅非常复杂,而且要找出一种表示可能发生的变化的好方法。
采纳答案by sbgoran
I wrote a little class that is doing what you want, you can test it here.
我写了一个小类,可以做你想做的,你可以在这里测试。
Only thing that is different from your proposal is that I don't consider [1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]
to be same, because I think that arrays are not equal if order of their elements is not same. Of course this can be changed if needed. Also this code can be further enhanced to take function as argument that will be used to format diff object in arbitrary way based on passed primitive values (now this job is done by "compareValues" method).
唯一与您的建议不同的是,我认为不[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 (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);
回答by drzaus
Using Underscore, a 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; })
Results in the parts of o1
that correspond but with different values in o2
:
结果在o1
对应的部分中,但在 中具有不同的值o2
:
{a: 1, b: 2}
It'd be different for a deep 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;
}
As pointed out by @Juhana in the comments, the above is only a diff a-->b and not reversible(meaning extra properties in b would be ignored). Use instead a-->b-->a:
正如@Juhana 在评论中指出的那样,上面只是一个差异 a-->b 并且不可逆(意味着 b 中的额外属性将被忽略)。使用 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());
See http://jsfiddle.net/drzaus/9g5qoxwj/for full example+tests+mixins
请参阅http://jsfiddle.net/drzaus/9g5qoxwj/以获取完整示例+测试+混合
回答by senornestor
I'd like to offer an ES6 solution...This is a one-way diff, meaning that it will return keys/values from o2
that are not identical to their counterparts in o1
:
我想提供一个 ES6 解决方案......这是一个单向差异,这意味着它将返回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]
}
}, {})
回答by toshiomagic
Using Lodash:
使用 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);
}
});
I don't use key/object/source but I left it in there if you need to access them. The object comparison just prevents the console from printing the differences to the console from the outermost element to the innermost element.
我不使用键/对象/源,但如果您需要访问它们,我将其留在那里。对象比较只是防止控制台从最外层元素到最内层元素将差异打印到控制台。
You can add some logic inside to handle arrays. Perhaps sort the arrays first. This is a very flexible solution.
您可以在内部添加一些逻辑来处理数组。也许先对数组进行排序。这是一个非常灵活的解决方案。
EDIT
编辑
Changed from _.merge to _.mergeWith due to lodash update. Thanks Aviron for noticing the change.
由于 lodash 更新,从 _.merge 更改为 _.mergeWith。感谢 Aviron 注意到这一变化。
回答by Anant
Here is a JavaScript library which you can use for finding diff between two JavaScript objects:
这是一个 JavaScript 库,您可以使用它来查找两个 JavaScript 对象之间的差异:
Github URL:https://github.com/cosmicanant/recursive-diff
Github 网址:https : //github.com/cosmicanant/recursive-diff
Npmjs url:https://www.npmjs.com/package/recursive-diff
npmjs 网址:https ://www.npmjs.com/package/recursive-diff
You can use recursive-diff library in browser as well as Node.js. For browser, do the following:
您可以在浏览器和 Node.js 中使用 recursive-diff 库。对于浏览器,请执行以下操作:
<script type="text" src="https://unpkg.com/[email protected]/dist/recursive-diff.min.js"/>
<script type="text/javascript">
const ob1 = {a:1, b: [2,3]};
const ob2 = {a:2, b: [3,3,1]};
const delta = recursiveDiff.getDiff(ob1,ob2);
/* console.log(delta) will dump following data
[
{path: ['a'], op: 'update', val: 2}
{path: ['b', '0'], op: 'update',val: 3},
{path: ['b',2], op: 'add', val: 1 },
]
*/
const ob3 = recursiveDiff.applyDiff(ob1, delta); //expect ob3 is deep equal to ob2
</script>
Whereas in node.js you can require 'recursive-diff' module and use it like below:
而在 node.js 中,您可以要求 'recursive-diff' 模块并使用它,如下所示:
const diff = require('recursive-diff');
const ob1 = {a: 1}, ob2: {b:2};
const diff = diff.getDiff(ob1, ob2);
回答by B T
These days, there are quite a few modules available for this. I recently wrote a module to do this, because I wasn't satisfied with the numerous diffing modules I found. Its called odiff
: https://github.com/Tixit/odiff. I also listed a bunch of the most popular modules and why they weren't acceptable in the readme of odiff
, which you could take a look through if odiff
doesn't have the properties you want. Here's an example:
现在,有很多模块可用于此。我最近编写了一个模块来执行此操作,因为我对发现的众多差异模块并不满意。它叫做odiff
:https: //github.com/Tixit/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}]}
]
*/
回答by Felix Furtmayr
const diff = require("deep-object-diff").diff;
let differences = diff(obj2, obj1);
There is an npm module with over 500k weekly downloads: https://www.npmjs.com/package/deep-object-diff
有一个每周下载量超过 50 万的 npm 模块:https: //www.npmjs.com/package/deep-object-diff
I like the object like representation of the differences - especially it is easy to see the structure, when it is formated.
我喜欢对象之类的差异表示 - 特别是在格式化时很容易看到结构。
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'
}
*/
回答by jarangseo
I've developed the Function named "compareValue()" in Javascript. it returns whether the value is same or not. I've called compareValue() in for loop of one Object. you can get difference of two objects in diffParams.
我在 Javascript 中开发了名为“compareValue()”的函数。它返回值是否相同。我在一个对象的 for 循环中调用了 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);
回答by AMember
I have used this piece of code for doing the task that you describe:
我已经使用这段代码来完成您描述的任务:
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;
}
this will get you a new object that will merge all the changes between the old object and the new object from your form
这将为您提供一个新对象,它将合并旧对象和表单中新对象之间的所有更改
回答by a11smiles
I know I'm late to the party, but I needed something similar that the above answers didn't help.
我知道我参加聚会迟到了,但我需要类似的东西,但上述答案没有帮助。
I was using Angular's $watch function to detect changes in a variable. Not only did I need to know whether a property had changed on the variable, but I also wanted to make sure that the property that changed was not a temporary, calculated field. In other words, I wanted to ignore certain properties.
我使用 Angular 的 $watch 函数来检测变量的变化。我不仅需要知道变量上的属性是否已更改,而且我还想确保更改的属性不是临时的计算字段。换句话说,我想忽略某些属性。
Here's the code: https://jsfiddle.net/rv01x6jo/
这是代码:https: //jsfiddle.net/rv01x6jo/
Here's how to use it:
以下是如何使用它:
// 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]);
Hope this helps someone.
希望这可以帮助某人。