如何比较两个对象并获得它们差异的键值对? [英] How to compare two objects and get key-value pairs of their differences?
问题描述
我有两个对象:
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
属性不一样。只有 x
有一个 c
属性,只有 y
有一个 d
属性。那么 ???
应该是什么?
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
的角度来看,输入对象之间的关系 a
和 b
可能完全是任意的。要传达哪个对象有所不同, diff
指定描述符左
和正确
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
- 哪些属性不同 -
b
,c
,d
- 哪个对象有所不同 -
左
和/或右
- 不同值 - 例如左
b
的值为2,右b
的值为3;或者左边的c
的值为3,右边的c
的值为 undefined
- which properties are different –
b
,c
, andd
- which object contributed the difference -
left
and/orright
- the "different" value - for example the left
b
has a value of 2, the rightb
has a value of 3; or the leftc
has a value of 3, the rightc
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
函数,它不关心它给出的输入对象。 left对象可以包含right对象不包含的键,反之亦然,但我们仍然必须检测来自任何一方的更改。从高层开始,这就是我们如何处理这个问题
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
我们采取片面差异使用 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
和正确
是用户可定义的。例如,如果您正在比较的对象之间存在已知关系,并且您希望在差异输出中提供更多描述性标签...
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 = (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' } }
在上面的例子中,它可能是更容易使用程序其他区域的输出,因为标签原始
和已修改
比<$ c更具描述性$ c>左和右
。
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 }
下面我们在合并
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
的地方,我们得到了它,我们获得了直观的深合并
功能。
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.
支持此功能需要对上面的代码进行非平凡的更改。但是,大多数结构和推理保持不变。例如,差异
完全不变
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 =
[ 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 ]
深度合并
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 } ] }
Su diff1
中的pporting数组更具挑战性
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) ? [] : {}
)
但是随着这些变化的到位,我们现在可以深入比较包含数组的对象 - 甚至是包含对象的数组! / p>
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.
递归联合
在这个相关的Q& A ,我们接受两个输入对象并计算一个递归 union
而不是 DIF f
In this related Q&A, we take two input objects and compute a recursive union
instead of a diff
这篇关于如何比较两个对象并获得它们差异的键值对?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!