我有两个对象:oldObj和newObj。
oldObj中的数据用于填充表单,而newObj是用户更改该表单中的数据并提交的结果。
这两个物体都很深。它们具有对象或对象数组等属性-它们可以有n层深,因此diff算法需要递归。
现在我不仅需要弄清楚从oldObj到newObj更改了什么(如添加/更新/删除),而且还需要知道如何最好地表示它。
到目前为止,我的想法只是构建一个通用的deepdiffbetweenobjects方法,该方法将返回窗体上的对象{add:{…},upd:{…},del:{…但我转念一想:以前一定有人需要它。
所以…有没有人知道一个库或一段代码可以做到这一点,并且可能有更好的方式来表示差异(以一种仍然是JSON可序列化的方式)?
更新:
我想到了一个更好的方法来表示更新的数据,通过使用与newObj相同的对象结构,但将所有属性值转换为窗体上的对象:
{type: '<update|create|delete>', data: <propertyValue>}
如果newObj。prop1 = 'new value'和oldObj。prop1 = 'old value'它将设置returnbj。Prop1 = {type: 'update', data: 'new value'}
更新2:
当我们处理数组的属性时,它会变得非常麻烦,因为数组[1,2,3]应该被计算为等于[2,3,1],这对于基于值的类型的数组(如string, int和bool)来说足够简单,但是当涉及到引用类型的数组(如对象和数组)时就很难处理了。
数组的示例应该是相等的:
[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]
不仅要检查这种类型的深度值相等相当复杂,而且要找出一种表示可能发生的变化的好方法。
我采用@sbgoran上面的答案,并根据我的情况修改了它,以将数组视为集合(即顺序对于diff不重要)
const deepDiffMapper = function () {
return {
VALUE_CREATED: "created",
VALUE_UPDATED: "updated",
VALUE_DELETED: "deleted",
VALUE_UNCHANGED: "unchanged",
map: function(obj1: any, obj2: any) {
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: obj2 === undefined ? obj1 : obj2
};
}
if (this.isArray(obj1) || this.isArray(obj2)) {
return {
type: this.compareArrays(obj1, obj2),
data: this.getArrayDiffData(obj1, obj2)
};
}
const diff: any = {};
for (const key in obj1) {
if (this.isFunction(obj1[key])) {
continue;
}
let value2 = undefined;
if (obj2[key] !== undefined) {
value2 = obj2[key];
}
diff[key] = this.map(obj1[key], value2);
}
for (const key in obj2) {
if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
continue;
}
diff[key] = this.map(undefined, obj2[key]);
}
return diff;
},
getArrayDiffData: function(arr1: Array<any>, arr2: Array<any>) {
const set1 = new Set(arr1);
const set2 = new Set(arr2);
if (arr1 === undefined || arr2 === undefined) {
return arr1 === undefined ? arr1 : arr2;
}
const deleted = [...arr1].filter(x => !set2.has(x));
const added = [...arr2].filter(x => !set1.has(x));
return {
added, deleted
};
},
compareArrays: function(arr1: Array<any>, arr2: Array<any>) {
const set1 = new Set(arr1);
const set2 = new Set(arr2);
if (_.isEqual(_.sortBy(arr1), _.sortBy(arr2))) {
return this.VALUE_UNCHANGED;
}
if (arr1 === undefined) {
return this.VALUE_CREATED;
}
if (arr2 === undefined) {
return this.VALUE_DELETED;
}
return this.VALUE_UPDATED;
},
compareValues: function (value1: any, value2: any) {
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: any) {
return Object.prototype.toString.call(x) === "[object Function]";
},
isArray: function (x: any) {
return Object.prototype.toString.call(x) === "[object Array]";
},
isDate: function (x: any) {
return Object.prototype.toString.call(x) === "[object Date]";
},
isObject: function (x: any) {
return Object.prototype.toString.call(x) === "[object Object]";
},
isValue: function (x: any) {
return !this.isObject(x) && !this.isArray(x);
}
};
}();
下面是一个JavaScript库,可以用来查找两个JavaScript对象之间的差异:
Github URL:
https://github.com/cosmicanant/recursive-diff
npm捷的url:北京方位www.npmjs.犯下/ recursiff
递归-diff库可以在浏览器中使用,也可以在基于Node.js的服务器端应用程序中使用。对于浏览器,它可以使用如下:
<script type="text" src="https://unpkg.com/recursive-diff@latest/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>
而在一个基于node.js的应用程序中,它可以这样使用:
const diff = require('recursive-diff');
const ob1 = {a: 1}, ob2: {b:2};
const diff = diff.getDiff(ob1, ob2);
从sbgoran的答案中得到扩展和简化的函数。
这允许深度扫描和发现数组的相似性。
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';
}