如何比较两个对象并获得它们差异的键值对?

bra*_*r19 7 javascript arrays angularjs

我有两个对象:

1)

{A: 10, B: 20, C: 30}
Run Code Online (Sandbox Code Playgroud)

2)

{A: 10, B: 22, C: 30}
Run Code Online (Sandbox Code Playgroud)

你可以看到:除了一件事之外,几乎是平等的:关键B值是不同的.

我怎样才能进入我的someNewArr关键价值差异?

someNewArr:( {B: 22}我从第二个对象获取值)

我正在使用角度,我的意思是这样的:

    var compareTwoObjects = function(initialObj, editedObj) {
        var resultArr = [];
        angular.forEach(initialObj, function(firstObjEl, firstObjInd) {
            angular.forEach(editedObj, function(secondObjEl, secondObjInd) {
                if (firstObjEl.key === secondObjEl.key && firstObjEl.value !== secondObjEl.value){
                    resultArr.push({firstObjEl.key: secondObjEl.value});
                }
            })
        });
    });
Run Code Online (Sandbox Code Playgroud)

Tha*_*you 20

递归差异

差不多3年后,我很高兴为这个问题提供一个更新的答案.

我们从两个不同的对象开始

const x =
  { a: 1, b: 2, c: 3 }

const y =
  { a: 1, b: 3, d: 4 }

console.log (diff (x, y))
// => ???
Run Code Online (Sandbox Code Playgroud)

两个对象具有相同的a属性.该b物业不一样.只有x一个c属性,只有y一个d属性.那究竟应该???是什么?

从的角度来看diff,我们的输入对象之间的关系a,并b可能完全是任意的.要传达哪个对象有所贡献,请diff分配描述符leftright

console.log (diff (x, y))
// { b: { left: 2, right: 3 }, c: { left: 3 }, d: { right: 4 } }
Run Code Online (Sandbox Code Playgroud)

在上面的输出中我们可以看到

  • 哪些属性不同- b,cd
  • 哪个对象有所贡献 - left和/或right
  • "不同"值 - 例如,左边b的值为2,右边b的值为3; 或左边c的值为3,右边c的值为undefined

在我们开始实现这个函数之前,我们首先要研究一个涉及深层嵌套对象的更复杂的场景

const x =
  { a: { b: { c: 1, d: 2, e: 3 } } }

const y =
  { a: { b: { c: 1, d: 3, f: 4 } } }

console.log (diff (x, y))
// { a: { b: { d: { left: 2, right: 3 }, e: { left: 3 }, f: { right: 4 } } } }
Run Code Online (Sandbox Code Playgroud)

如上所示,diff返回与输入匹配的结构.最后,我们期望diff两个相同的对象返回"空"结果

const x1 =
  { a: 1, b: { c: { d: 2 } } }

const x2 =
  { a: 1, b: { c: { d: 2 } } }

console.log (diff (x1, x2))
// {}
Run Code Online (Sandbox Code Playgroud)

上面我们描述了一个diff不关心它给出的输入对象的函数."left"对象可以包含"right"对象不包含的键,反之亦然,但我们仍然必须检测来自任何一方的更改.从高层开始,这就是我们如何处理这个问题

const diff = (x = {}, y = {}) =>
  merge
    ( diff1 (x, y, "left")
    , diff1 (y, x, "right")
    ) 
Run Code Online (Sandbox Code Playgroud)

DIFF1

我们采用"单侧"差异diff1描述为"左"关系,我们采取另一个单侧差异与输入对象颠倒描述为"正确"关系,然后我们merge两个结果在一起

我们的工作在我们现在更容易完成的任务中分工.diff1只需要检测一半必要的变化并merge简单地结合结果.我们先从开始diff1

const empty =
  {}

const isObject = x =>
  Object (x) === x

const diff1 = (left = {}, right = {}, rel = "left") =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, diff1 (v, right[k], rel) ]
            : right[k] !== v
              ? [ k, { [rel]: v } ]
              : [ k, empty ]
      )
    .reduce
      ( (acc, [ k, v ]) =>
          v === empty
            ? acc
            : { ...acc, [k]: v }
      , empty
      )
Run Code Online (Sandbox Code Playgroud)

diff1接受两个输入对象和一个关系描述符rel.此描述符默认为"left"比较的默认"方向".下面,请注意diff1只提供我们需要的一半结果.在第二次调用中反转参数diff1提供另一半.

const x =
  { a: 1, b: 2, c: 3 }

const y =
  { a: 1, b: 3, d: 4 }

console.log (diff1 (x, y, "left"))
// { b: { left: 2 }, c: { left: 3 } }

console.log (diff1 (y, x, "right"))
// { b: { right: 3 }, d: { right: 4 } }
Run Code Online (Sandbox Code Playgroud)

另外值得注意的是关系标签,"left"并且"right"是用户可定义的.例如,如果您正在比较的对象之间存在已知关系,并且您希望在diff输出中提供更多描述性标签...

const customDiff = (original = {}, modified = {}) =>
  merge
    ( diff1 (x, y, "original")
    , diff1 (y, x, "modified")
    )

customDiff
    ( { host: "localhost", port: 80 }
    , { host: "127.0.0.1", port: 80 }
    )
// { host: { original: 'localhost', modified: '127.0.0.1' } }
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,它可能会更容易与输出工作在程序的其他地区,因为标签originalmodified比更具描述性leftright.

合并

剩下的就是将两个半差异合并成一个完整的结果.我们的merge函数也一般工作,并接受任何两个对象作为输入.

const x =
  { a: 1, b: 1, c: 1 }

const y =
  { b: 2, d: 2 }

console.log (merge (x, y))
// { a: 1, b: 2, c: 1, d: 2 }
Run Code Online (Sandbox Code Playgroud)

如果每个对象都包含一个其值也是对象的属性,则merge也会重复并合并嵌套对象.

const x =
  { a: { b: { c: 1, d: 1 } } }

const y =
  { a: { b: { c: 2, e: 2 } }, f: 2 }

console.log (merge (x, y))
// { a: { b: { c: 2, d: 1, e: 2 } }, f: 2 }
Run Code Online (Sandbox Code Playgroud)

下面我们编码我们的意图 merge

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .reduce
      ( (acc, [ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? { ...acc, [k]: merge (left [k], v) }
            : { ...acc, [k]: v }
      , left
      )
Run Code Online (Sandbox Code Playgroud)

这就是整个套件和架子!展开下面的代码段,在您自己的浏览器中运行代码演示

const empty =
  {}

const isObject = x =>
  Object (x) === x

const diff1 = (left = {}, right = {}, rel = "left") =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, diff1 (v, right[k], rel) ]
            : right[k] !== v
              ? [ k, { [rel]: v } ]
              : [ k, empty ]
      )
    .reduce
      ( (acc, [ k, v ]) =>
          v === empty
            ? acc
            : { ...acc, [k]: v }
      , empty
      )

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .reduce
      ( (acc, [ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? { ...acc, [k]: merge (left [k], v) }
            : { ...acc, [k]: v }
      , left
      )

const diff = (x = {}, y = {}) =>
  merge
    ( diff1 (x, y, "left")
    , diff1 (y, x, "right")
    )

const x =
  { a: { b: { c: 1, d: 2, e: 3 } } }

const y =
  { a: { b: { c: 1, d: 3, f: 4 } } }

console.log (diff (x, y))
// { a: { b: { d: { left: 2, right: 3 }, e: { left: 3 }, f: { right: 4 } } } }

console.log (diff (diff (x,y), diff (x,y)))
// {} 
Run Code Online (Sandbox Code Playgroud)

备注

回顾我们的diff功能,我想强调其设计的一个重要部分.很大一部分工作是由merge完全独立的功能处理的diff,但它本身就是一个难以破解的坚果.因为我们将我们的关注点分解为单个函数,所以现在很容易在程序的其他区域重用它们.我们想要的地方diff,我们得到了它,我们merge免费获得直观的深层功能.


extra:支持数组

我们的diff函数非常方便,因为它可以抓取深层嵌套的对象,但是如果我们的一个对象属性是一个数组呢?如果我们可以使用相同的技术来区分数组,那就太好了.

支持此功能需要对上面的代码进行重要的更改.但是,大多数结构和推理保持不变.例如,diff完全没有变化

// unchanged
const diff = (x = {}, y = {}) =>
  merge
    ( diff1 (x, y, "left")
    , diff1 (y, x, "right")
    )
Run Code Online (Sandbox Code Playgroud)

为了支持数组merge,我们引入了一个变异助手mut,它将一[ key, value ]对分配给给定的对象,o.数组也被视为对象,因此我们可以使用相同的mut函数更新数组和对象

const mut = (o, [ k, v ]) =>
  (o [k] = v, o)

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? [ k, merge (left [k], v) ]
            : [ k, v ]
      )
    .reduce (mut, left)
Run Code Online (Sandbox Code Playgroud)

浅合并按预期工作

const x =
  [ 1, 2, 3, 4, 5 ]

const y =
  [ 0, 0, 0 ]

const z =
  [ , , , , , 6 ]

console.log (merge (x, y))
// [ 0, 0, 0, 4, 5 ]

console.log (merge (y, z))
// [ 0, 0, 0, <2 empty items>, 6 ]

console.log (merge (x, z))
// [ 1, 2, 3, 4, 5, 6 ]
Run Code Online (Sandbox Code Playgroud)

并深深融合

const x =
  { a: [ { b: 1 }, { c: 1 } ] }

const y =
  { a: [ { d: 2 }, { c: 2 }, { e: 2 } ] }

console.log (merge (x, y))
// { a: [ { b: 1, d: 2 }, { c: 2 }, { e: 2 } ] }
Run Code Online (Sandbox Code Playgroud)

支持阵列diff1更具挑战性

const diff1 = (left = {}, right = {}, rel = "left") =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, diff1 (v, right[k], rel) ]
            : right[k] !== v
              ? [ k, { [rel]: v } ]
              : [ k, {} ]
      )
    .filter
      ( ([ k, v ]) =>
          Object.keys (v) .length !== 0
      )
    .reduce
      ( mut
      , isArray (left) && isArray (right) ? [] : {}
      )
Run Code Online (Sandbox Code Playgroud)

但是随着这些变化的到位,我们现在可以深入比较包含数组的对象 - 甚至是包含对象的数组!

const x =
  { a: 1, b: [ { c: 1 }, { d: 1 }, { e: 1 } ] }

const y =
  { a: 1, b: [ { c: 2 }, { d: 1 }, 5, 6 ], z: 2 }

console.log (diff (x, y))
// { b:
//     [ { c: { left: 1, right: 2 } }
//     , <1 empty item>
//     , { left: { e: 1 }, right: 5 }
//     , { right: 6 }
//     ]
// , z: { right: 2 } 
// }
Run Code Online (Sandbox Code Playgroud)

因为diff1根据其输入类型仔细改变其行为,我们免费获得数组差异

const x =
  [ 1, 2, 3, 4 ]

const y =
  [ 1, 2, 9 ]

const z =
  [ 1, 2, 9 ]

console.log (diff (x, y))
// [ <2 empty items>, { left: 3, right: 9 }, { left: 4 } ]

console.log (diff (y, z))
// []
Run Code Online (Sandbox Code Playgroud)

在下面的浏览器中运行完整的程序

const isObject = x =>
  Object (x) === x

const isArray =
  Array.isArray

const mut = (o, [ k, v ]) =>
  (o [k] = v, o)

const diff1 = (left = {}, right = {}, rel = "left") =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, diff1 (v, right[k], rel) ]
            : right[k] !== v
              ? [ k, { [rel]: v } ]
              : [ k, {} ]
      )
    .filter
      ( ([ k, v ]) =>
          Object.keys (v) .length !== 0
      )
    .reduce
      ( mut
      , isArray (left) && isArray (right) ? [] : {}
      )

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? [ k, merge (left [k], v) ]
            : [ k, v ]
      )
    .reduce (mut, left)


const diff = (x = {}, y = {}) =>
  merge
    ( diff1 (x, y, "left")
    , diff1 (y, x, "right")
    )

const x =
  { a: 1, b: [ { c: 1 }, { d: 1 }, { e: 1 } ] }

const y =
  { a: 1, b: [ { c: 2 }, { d: 1 }, 5, 6 ], z: 2 }

console.log (diff (x, y))
// { b:
//     [ { c: { left: 1, right: 2 } }
//     , <1 empty item>
//     , { left: { e: 1 }, right: 5 }
//     , { right: 6 }
//     ]
// , z: { right: 2 } 
// }
Run Code Online (Sandbox Code Playgroud)

浅滩

此答案的先前版本提供了一个对象diff函数,用于比较具有相同键的对象并将对象与不同的键进行比较,但两种解决方案都没有在嵌套对象上递归执行差异.

递归联盟

这个相关的Q&A中,我们采用两个输入对象并计算递归intersect而不是adiff

  • 检查:`console.log(diffObject({A:10,B:20,C:30,D:90},{A:10,B:22,C:30})); (2认同)

Bha*_*lli 0

这将返回第一个参数相对于第二个参数的差异。不过,我在这里没有使用 angular.forEach 。

var x = {
   a : 1,
   b:2,
  c :3,
  d:4
 }

var y = {
   a : 1,
   b:4,
  c :3,
  d : 5
 };


var diff = function(x,y){
  var target = {};   
  var diffProps = Object.keys(x).filter(function(i){
    if(x[i] !== y[i]){
      return true;
    }
    return false;
   }).map(function(j){
       var obj = {};
       obj[j] = x[j];
       target = Object.assign(target,obj)
  });
   return target;
};


console.log(diff(x,y));
Run Code Online (Sandbox Code Playgroud)