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

时间:2015-10-20 09:24:53

标签: javascript arrays angularjs

我有两个对象:

1)

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

2)

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

正如您所看到的:除了一件事之外几乎相同:键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});
                }
            })
        });
    });

7 个答案:

答案 0 :(得分:18)

递归差异

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

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

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属性。那么???究竟应该是什么?

diff的角度来看,输入对象ab之间的关系可能完全是任意的。要传达哪个对象有所不同,diff指定描述符leftright

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

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

  • 哪些属性不同 - bcd
  • 哪个对象有所不同 - 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 } } } }

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

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

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

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

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

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

<强> DIFF1

我们采取单边&#34;差异使用diff1描述为&#34;左&#34;关系,我们采取另一个单侧差异与输入对象反转描述为&#34;右&#34;关系,然后我们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
      )

diff1接受两个输入对象和一个关系描述符rel。此描述符默认为"left",这是默认的&#34;方向&#34;比较。下面,请注意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 } }

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

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

在上面的示例中,在程序的其他区域使用输出可能更容易,因为标签originalmodifiedleft和{{1}更具描述性}。

<强>合并

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

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 } 也会重复并合并嵌套对象。

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 }

中编码我们的意图
merge

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

&#13;
&#13;
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
      )
&#13;
&#13;
&#13;

<强>备注

当我们回顾我们的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完全分开,而tough nut to crack则独立于diff。因为我们将我们的关注点分解为单一函数,所以现在很容易在程序的其他方面重用它们。在我们需要diff的地方,我们得到了它,并且我们免费获得了直观的深merge功能。

额外:支持数组

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

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

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

为了支持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)

浅层合并按预期工作

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

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

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

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根据其输入类型小心地改变了它的行为,所以我们得到免费的数组差异

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))
// []

在浏览器中运行完整程序

&#13;
&#13;
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 } 
// }
&#13;
&#13;
&#13;

浅差异

这个答案的previous version提供了一个对象diff函数,用于比较具有相同键的对象并使用不同的键比较对象,但两种解决方案都没有在嵌套对象上递归执行差异。

递归联盟

this related Q&A中,我们采用两个输入对象并计算递归union而不是diff

答案 1 :(得分:0)

这将返回第一个参数相对于第二个参数的diff。我不是在这里使用angular.forEach。

&#13;
&#13;
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));
&#13;
&#13;
&#13;

答案 2 :(得分:0)

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

这是DEMO

答案 3 :(得分:0)

此解决方案不是有角度的,但可能有所帮助。

任意数量的密钥需要2个对象,并且它们不需要包含相同的密钥。

**输出:** 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 );

希望它有所帮助。

答案 4 :(得分:0)

我希望这会对你有所帮助。我是用 jQuery each函数完成的。

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
    }

});

答案 5 :(得分:0)

解决方案非常简单,

初始化数组,

var resultArray = [];

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

最后运行简单的代码

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

最后收集答案

console.log(resultArray);

答案 6 :(得分:0)

尝试一下

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