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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33232823/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 14:48:50  来源:igfitidea点击:

How to compare two objects and get key-value pairs of their differences?

javascriptarraysangularjs

提问by brabertaser19

I have two objects:

我有两个对象:

1)

1)

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

2)

2)

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

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

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

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

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

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

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

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

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

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

回答by Thank you

recursive diff

递归差异

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

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

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))
// => ???

Both objects have the same aproperty. The bproperty is not the same. Only xhas a cproperty, and only yhas a dproperty. So what should ???be exactly?

两个对象具有相同的a属性。该b属性是不一样的。只有x一个c属性,并且只有y一个d属性。那么具体应该???是什么呢?

From the perspective of diff, the relationship between our input objects aand bcould be completely arbitrary. To communicate the which object contributes a difference, diffassigns descriptors leftand right

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

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

In the output above we can see

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

  • which properties are different – b, c, and d
  • which object contributed the difference - leftand/or right
  • the "different" value - for example the left bhas a value of 2, the right bhas a value of 3; or the left chas a value of 3, the right chas a value of undefined
  • 哪些属性不同- bcd
  • 哪个对象产生了差异 -left和/或right
  • “不同”值 - 例如左边b的值为 2,右边b的值为 3;或者左边c的值为 3,右边c的值为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 } } } }

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

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

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

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

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

Above we describe a difffunction 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

上面我们描述了一个diff不关心给定输入对象的函数。“左”对象可以包含“右”对象不包含的键,反之亦然,但我们仍然必须检测任一侧的变化。从高层开始,这就是我们解决问题的方式

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

diff1

差异1

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

我们采用diff1描述为“左”关系的“单边”差异,我们采用另一个将输入对象反转描述为“右”关系的单边差异,然后我们merge将两个结果放在一起

Our work is divided for us in tasks that are easier to accomplish now. diff1only needs to detect half of the necessary changes and mergesimply combines the results. We'll start with diff1

我们的工作被分配给现在更容易完成的任务。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
      )

diff1accepts two input objects and a relationship descriptor, rel. This descriptor defaults to "left"which is the default "orientation" of the comparison. Below, notice that diff1only provides half of the result we need. Reversing the arguments in a secondcall to diff1provides the other half.

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

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

同样值得注意的是关系标签"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' } }

In the above example, it may be easier to work with the output in other areas of your program because labels originaland modifiedare more descriptive than leftand right.

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

merge

合并

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

剩下的就是将两个半差异合并为一个完整的结果。我们的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 }

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

如果每个对象都包含一个属性,其值也是一个对象,则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 }

Below we encode our intentions in merge

下面我们将我们的意图编码为 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)))
// {} 

remarks

评论

As we look back at our difffunction, I want to highlight one important part of its design. A good portion of the work is handled by the mergefunction which is completely separate from diff, yet a tough nut to crackon 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 mergefunctionality for free.

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



extra: support for arrays

额外:支持数组

Our difffunction 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, diffis completely unchanged

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

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

To support arrays in merge, we introduce a mutation helper mutwhich 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 mutfunction

为了支持 中的数组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)

Shallow merges work as expected

浅合并按预期工作

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 ]

And deep merges too

并且也深度合并

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

Supporting arrays in diff1is considerably more challenging

支持数组 indiff1更具挑战性

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

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

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

shallow diff

浅差异

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

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

recursive intersection

递归交集

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

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

回答by Bhargav Ponnapalli

This will return the diff of the first argument with respect to the second argument. I am not using angular.forEach here though.

这将返回第一个参数相对于第二个参数的差异。不过,我没有在这里使用 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));

回答by Kalhan.Toress

$scope.ar1 = {A: 10, B: 20, C: 30};

$scope.ar2 = {A: 10, B: 22, C: 30};

$scope.newObj = {};
angular.forEach($scope.ar1, function(v, i) {
    // if ar2[i] is exists and ar2[i] != v then put that value to newObj
    if ($scope.ar2[i] && $scope.ar2[i] != v) {
        $scope.newObj[i] = $scope.ar2[i];
    }
});

console.log($scope.newObj);

here is the DEMO

这是演示

回答by web-nomad

This solution is not in angular, but it might help.

这个解决方案不是有角度的,但它可能会有所帮助。

It will take 2 objects with any number of keys, and they need not contain the same keys.

它将需要 2 个具有任意数量键的对象,并且它们不需要包含相同的键。

**Output: ** The key:value pairs which are present in only one object and not the other and the key:value pairs which are present in both objects but the values are different.

**输出: ** The key:value pairs which are present in only one object and not the other and the key:value pairs which are present in both objects but the values are different.

var obj1 = {A: 10, B: 20, C: 30, E: 40};
var obj2 = {A: 11, B: 20, C: 30, D: 50};
var finalObject = {};

$( document ).ready(function() {
    var keysOfObj1 = Object.keys( obj1 );
    var keysOfObj2 = Object.keys( obj2 );

    var keys = [];
    keys = $( keysOfObj1 ).not( keysOfObj2 ).get(); // keys of first object not in second object

    for( var i=0;i<keys.length;i++ ) {
        finalObject[ keys[ i ] ] = obj1[ keys[ i ] ];
    }

    keys.length = 0; // reset the temp array

    keys = $( keysOfObj2 ).not( keysOfObj1 ).get(); // keys of second object not in first object

    for( var i=0;i<keys.length;i++ ) {
        finalObject[ keys[ i ] ] = obj2[ keys[ i ] ];
    }

    keys.length = 0; // reset the temp array again

    if( keysOfObj1.length != keysOfObj2.length ) {
        // case already handled above
    }

    for( var i in obj1 ) {
        if( obj1.hasOwnProperty( i ) ) {
            if( obj2.hasOwnProperty( i ) ) {
                if( obj1[ i ] != obj2[ i ] ) {
                    finalObject[ i ] = obj2[ i ];
                } else {
                    // the property has the same value in both objects, all is well...
                }
            } else {
                // case already handled above
            }
        } else {
            // case already handled above
        }
    }
    console.log( obj1 );
    console.log( obj2 );
    console.log( finalObject );

Hope it helps.

希望能帮助到你。

回答by Mehmood

I hope this will help you. I did it with jQueryeachfunction.

我希望这能帮到您。我是用jQueryeach函数完成的。

var a = {A: 10, B: 20, C: 30};
var b = {A: 10, B: 22, C: 30};
var hasObj = false; //Declaring variable outside for onetime memory allocation.

$.each(b, function(keyOfB, valOfB) {

    hasObj = false;  //Assigning false for each parent loop

    $.each(a, function(keyOfA, valOfA) {
        if (keyOfA == keyOfB && valOfA == valOfB) {
            hasObj = true;
            return false; //If key and value mathed loop will break and no remaining items of second array will be check.
        }
    });

    if (hasObj == false) {
        console.log(keyOfB + "--" + valOfB); //Printing the unmatched key and value
    }

});

回答by theProgrammer

The solution is quite simple,

解决方法很简单,

Initialize your array,

初始化你的数组,

var resultArray = [];

then cycle through the keys of your object, using one as a reference (Highly assuming the objects have the same keys, but you want to check on keys holding different values)

然后循环遍历对象的键,使用一个作为参考(高度假设对象具有相同的键,但您想检查具有不同值的键)

and finally run the simple code

最后运行简单的代码

for(let key in obj){
    // console.log(key);
    if(obj[key]  !== this.profileObject[key] ){
      resultArray.push(key);
    }
}

And collect your answer at the end

并在最后收集你的答案

console.log(resultArray);

回答by Debojyoti

Try this

尝试这个

function getNewProperties(prevObj, newObj) {
  const prevObjProperties = Object.keys(prevObj);
  const newObjProperties = Object.keys(newObj);
  const newProperties = newObjProperties.filter(prop => prevObjProperties.indexOf(prop) === -1);
  return newProperties;
}