我有两个对象: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]]

不仅要检查这种类型的深度值相等相当复杂,而且要找出一种表示可能发生的变化的好方法。


当前回答

如今,有相当多的模块可用于此。我最近写了一个模块来做到这一点,因为我不满意我发现的大量不同的模块。它被称为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}]}
]
*/

其他回答

我已经使用这段代码来完成你所描述的任务:

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

这将为您提供一个新对象,该对象将合并窗体中的旧对象和新对象之间的所有更改

另一个基于lodash的解决方案,它有点特定于我们想要查看对象更新的diff的情况:

const diff = return {
  old: _.pickBy(oldObject, (value, key) => { return !_.isEqual(value, newObject[key]); }),
  new: _.pickBy(newObject, (value, key) => { return !_.isEqual(oldObject[key], value); })
}

没有使用_。omitBy是因为性能影响。

使用Lodash:

_。合并(oldObj, newObj, function (objectValue, sourceValue, key, object, source) { 如果(!)(_。isEqual(objectValue, sourceValue)) && (Object(objectValue) ! console.log(key + "\n Expected: " + sourceValue + "\n Actual: " + objectValue); } });

我不使用key/object/source,但我把它留在那里,如果你需要访问它们。 对象比较只是防止控制台将最外层元素与最内部元素之间的差异打印到控制台。

您可以在内部添加一些逻辑来处理数组。也许先对数组排序。这是一个非常灵活的解决方案。

EDIT

由_更改。合并到_。mergeWith由于lodash更新。感谢Aviron注意到这个变化。

2022年更新:

我想出了一个非常简单的算法来解决大多数边缘情况:

使物体变平 简单地比较两个扁平对象并创建一个扁平的diff对象 平展diff对象

如果你保存了被压平的物体,你就可以重复使用它,并在你真正需要的时候进行“反压平…”

let oldObject = {var1:'value1', var2:{ var1:'value1', var2:'value2'},var3:'value3'};
let newObject = {var2:{ var1:'value11', var3:'value3'},var3:'value3'};

let flatOldObject = flattenObject(oldObject)
/*
{
 'var1':'value1',
 'var2.var1':'value1',
 'var2.var2':'value2',
 'var3':'value3' 
}
*/
let flatNewObject = flattenObject(newObject)
/*
{
 'var2.var1':'value11',
 'var2.var3':'value3',
 'var3':'value3'
}
*/
let flatDiff = diffFlatten(flatOldObject, flatNewObject)
let [updated,removed] = flatDiff
/*
updated = {
 'var2.var1':'value11',
 'var2.var3':'value3'
}
removed = {
'var1':'value1'
}
*/

当然,您可以为这些步骤提供您的实现。但我的想法是:

实现

function flattenObject(obj) {
 const object = Object.create(null);
 const path = [];
 const isObject = (value) => Object(value) === value;

 function dig(obj) {
  for (let [key, value] of Object.entries(obj)) {
    path.push(key);
    if (isObject(value)) dig(value);
    else object[path.join('.')] = value;
    path.pop();
  }
 }

 dig(obj);
 return object;
}
function diffFlatten(oldFlat, newFlat) {
    const updated = Object.assign({}, oldFlat);
    const removed = Object.assign({}, newFlat);

    /**delete the unUpdated keys*/
    for (let key in newFlat) {
        if (newFlat[key] === oldFlat[key]) {
             delete updated[key];
             delete removed[key];
        }
    }

    return [updated, removed];

}
function unflatenObject(flattenObject) {
    const unFlatten = Object.create(null);
    for (let [stringKeys, value] of Object.entries(flattenObject)) {
        let chain = stringKeys.split('.')
        let object = unFlatten

        for (let [i, key] of chain.slice(0, -1).entries()) {
            if (!object[key]) {
                let needArray = Number.isInteger(Number(chain[+i + 1]))
                object[key] = needArray ? [] : Object.create(null)
            }
            object = object[key];
        }
        let lastkey = chain.pop();
        object[lastkey] = value;
    }
    return unFlatten;
}
var base = [
{"value": "01", "label": "Pendências"},
{"value": "02", "label": "Ambulatório"},
{"value": "03", "label": "Urgência"},
{"value": "04", "label": "Clínica Médica"},
{"value": "05", "label": "Revisão"},
{"value": "06", "label": "Imagens"},
];

var used = [
{"value": "01", "label": "Pendências"},
{"value": "02", "label": "Ambulatório"},
{"value": "03", "label": "Urgência"},
{"value": "04", "label": "Clínica Médica"},
];

function diff(obj1,obj2) {
        var temp = JSON.stringify(obj2.map((x)=> x.value));
    return obj1.filter((y)=> temp.indexOf(y.value)<0 && y);
}


var result = diff(base, used); 
console.clear();
console.log('RESULTADO');
console.log(result);

codeped