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

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


当前回答

const diff = require("deep-object-diff").diff;
let differences = diff(obj2, obj1);

有一个每周下载量超过50万的npm模块:https://www.npmjs.com/package/deep-object-diff

我喜欢对象式的差异表示-特别是当它形成时,很容易看到结构。

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'
}
*/

其他回答

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

我在这里跌跌撞撞地试图寻找一种方法来区分两个对象之间的区别。这是我使用Lodash的解决方案:

// Get updated values (including new values)
var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value));

// Get updated values (excluding new values)
var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value)));

// Get old values (by using updated values)
var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {});

// Get newly added values
var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key));

// Get removed values
var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key));

// Then you can group them however you want with the result

Code snippet below: var last = { "authed": true, "inForeground": true, "goodConnection": false, "inExecutionMode": false, "online": true, "array": [1, 2, 3], "deep": { "nested": "value", }, "removed": "value", }; var curr = { "authed": true, "inForeground": true, "deep": { "nested": "changed", }, "array": [1, 2, 4], "goodConnection": true, "inExecutionMode": false, "online": false, "new": "value" }; // Get updated values (including new values) var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value)); // Get updated values (excluding new values) var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value))); // Get old values (by using updated values) var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {}); // Get newly added values var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key)); // Get removed values var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key)); console.log('oldValues', JSON.stringify(oldValues)); console.log('updatedValuesIncl', JSON.stringify(updatedValuesIncl)); console.log('updatedValuesExcl', JSON.stringify(updatedValuesExcl)); console.log('newCreatedValues', JSON.stringify(newCreatedValues)); console.log('deletedValues', JSON.stringify(deletedValues)); <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

这只会返回属性改变的新对象。(省略和isEmpty是lodash中的函数)

export const getObjectDifference = <T extends {}>(originalObject: T, newObject: T) => {
const sameProperties: string[] = [];

Object.entries(originalObject).forEach(original => {
    Object.entries(newObject).forEach(newObj => {
        if (original[0] === newObj[0]) {
            if (original[1] === newObj[1])
                sameProperties.push(newObj[0]);
        }
    });
});

const objectDifference: T = omit(newObject, sameProperties) as T;
if (isEmpty(objectDifference))
    return null;
else
    return objectDifference; }

使用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注意到这个变化。

使用下划线,一个简单的差异:

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中的额外属性将被忽略)。用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());

完整的示例+tests+mixins请参见http://jsfiddle.net/drzaus/9g5qoxwj/