如何比较两个对象并获得它们差异的键值对? [英] How to compare two objects and get key-value pairs of their differences?

查看:72
本文介绍了如何比较两个对象并获得它们差异的键值对?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个对象:

1)

{A: 10, B: 20, C: 30}

2)

{A: 10, B: 22, C: 30}

如您所见:几乎相同,除了一件事:键 B 值不同.

as you can see: there are almost equal, except one thing: key B value is different.

我怎样才能进入我的 someNewArr 键值差异对等?

How can i get into my someNewArr key-value pare of differences?

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

like someNewArr: {B: 22} (i get values from second object)

我使用的是 angular,我的意思是这样的:

i'm using angular, and i mean something like this:

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

推荐答案

递归差异

将近 3 年后,我很高兴为这个问题提供一个全新的答案.

Almost 3 years later, I'm happy to provide a refreshed answer to this question.

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

We start with two objects that are different

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

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

console.log (diff (x, y))
// => ???

两个对象具有相同的 a 属性.b 属性不一样.只有 xc 属性,只有 yd 属性.那么 ??? 到底应该是什么?

Both objects have the same a property. The b property is not the same. Only x has a c property, and only y has a d property. So what should ??? be exactly?

diff的角度来看,我们的输入对象ab之间的关系可以是完全任意的.为了传达哪个对象造成差异,diff 分配描述符 leftright

From the perspective of diff, the relationship between our input objects a and b could be completely arbitrary. To communicate the which object contributes a difference, diff assigns descriptors left and right

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

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

In the output above we can see

  • 哪些属性不同——bcd
  • 哪个对象产生了差异 - left 和/或 right
  • 不同"value - 例如左边的 b 的值为 2,右边的 b 的值为 3;或者左边的 c 的值为 3,右边的 c 的值为 undefined
  • which properties are different – b, c, and d
  • which object contributed the difference - left and/or right
  • the "different" value - for example the left b has a value of 2, the right b has a value of 3; or the left c has a value of 3, the right c has a value of undefined

在我们进入这个函数的实现之前,我们将首先检查一个涉及深度嵌套对象的更复杂的场景

Before we get into the implementation of this function, we'll first examine a more complex scenario involving deeply nested objects

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

正如我们在上面看到的,diff 返回一个与我们的输入匹配的结构.最后,我们期望两个相同对象的 diff 返回一个空"对象.结果

As we can see above, diff returns a structure that matches our inputs. And finally we expect the diff of two objects that are the same to return an "empty" result

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

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

console.log (diff (x1, x2))
// {}

上面我们描述了一个 diff 函数,它不关心给定的输入对象.左"对象可以包含正确"的键.object 不包含,反之亦然,但我们仍然必须检测任何一方的变化.从高层开始,这就是我们将如何解决问题

Above we describe a diff function that does not care about the input objects it is given. The "left" object can contain keys the "right" object does not contain, and vice versa, yet we still must detect changes from either side. Starting from a high-level, this is how we'll be approaching the problem

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

diff1

我们采取片面"的方式diff 使用 diff1 描述为左";关系,我们采用另一个单边差异,输入对象被颠倒,描述为右"关系,然后我们合并两个结果在一起

We take a "one-sided" diff using diff1 described as the "left" relation, and we take another one-sided diff with the input objects reversed described as the "right" relation, then we merge the two results together

我们的工作分为现在更容易完成的任务.diff1 只需要检测一半的必要更改,merge 只需将结果组合起来.我们将从 diff1

Our work is divided for us in tasks that are easier to accomplish now. diff1 only needs to detect half of the necessary changes and merge simply combines the results. We'll start with 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
      )

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

diff1 accepts two input objects and a relationship descriptor, rel. This descriptor defaults to "left" which is the default "orientation" of the comparison. Below, notice that diff1 only provides half of the result we need. Reversing the arguments in a second call to diff1 provides the other half.

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

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

Also worth noting is the relationship labels "left" and "right" are user-definable. For example, if you have a known relationship between the objects you're comparing and you wish to provide more descriptive labels in the diff output ...

const customDiff = (x = {}, y = {}) =>
  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' } }

在上面的例子中,在程序的其他区域处理输出可能更容易,因为标签 originalmodifiedleft 更具描述性right.

In the above example, it may be easier to work with the output in other areas of your program because labels original and modified are more descriptive than left and right.

合并

剩下的就是将两个半差异合并为一个完整的结果.我们的 merge 函数也可以通用并接受任意两个对象作为输入.

All that remains is merging the two half diffs into a complete result. Our merge function also works generically and accepts any two objects as input.

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 }

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

In the event each object contains a property whose value is also an object, merge will recur and merge the nested objects as well.

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 }

下面我们在 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
      )

这就是整个工具包和一堆东西!展开下面的代码片段,在您自己的浏览器中运行代码演示

And that's the whole kit and caboodle! Expand the code snippet below to run a code demonstration in your own browser

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)))
// {} 

备注

当我们回顾我们的 diff 函数时,我想强调其设计的一个重要部分.大部分工作由 merge 函数处理,该函数与 diff 完全分离,但难以自行破解.因为我们将我们的关注点分成了单个函数,所以现在很容易在程序的其他区域重用它们.我们想要diff 的地方,我们得到了,而且我们免费获得了直观的深度merge 功能.

As we look back at our diff function, I want to highlight one important part of its design. A good portion of the work is handled by the merge function which is completely separate from diff, yet a tough nut to crack on its own. Because we separated our concerns into singular functions, it's now easy to reuse them in other areas of your program. Where we wanted diff, we got it, and we got intuitive deep merge functionality for free.

额外:支持数组

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

Our diff function is very convenient as it can crawl deeply nested objects, but what if one of our object properties is an array? It'd be nice if we could diff arrays using the same technique.

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

Supporting this feature requires non-trivial changes to the code above. However, the majority of the structure and reasoning stays the same. For example, diff is completely unchanged

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

为了在 merge 中支持数组,我们引入了一个变异助手 mut,它将一个 [ key, value ] 对分配给给定的对象,o.数组也被认为是对象,所以我们可以使用相同的 mut 函数

To support arrays in merge, we introduce a mutation helper mut which assigns a [ key, value ] pair to a given object, o. Arrays are considered objects too, so we can update both arrays and objects using the same mut function

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)

浅合并按预期工作

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

const y =
  [ , , , , , 6 ]

const z =
  [ 0, 0, 0 ]

console.log (merge (x, y))
// [ 1, 2, 3, 4, 5, 6 ]

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

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

还有深度合并

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

diff1 中支持数组更具挑战性

Supporting arrays in diff1 is considerably more challenging

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) ? [] : {}
      )

但是有了这些变化,我们现在可以深入比较包含数组的对象——甚至包含对象的数组!

But with these changes in place, we can now deeply compare objects that contain arrays – and even arrays containing objects!

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

因为 diff1 根据输入类型小心地改变它的行为,我们可以免费获得数组差异

Because diff1 carefully changes its behavior based on its input types, we get array diffing for free

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 the full program in your browser below

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

浅差异

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

The previous version of this answer provided an object diff function for comparing objects with the same keys and comparing objects with different keys, but neither solution performed the diff recursively on nested objects.

递归交集

这个相关问答中,我们取两个输入对象并计算递归intersect 而不是 diff.

In this related Q&A, we take two input objects and compute a recursive intersect instead of diff.

这篇关于如何比较两个对象并获得它们差异的键值对?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆