【问题标题】:How to compare two objects and get key-value pairs of their differences?如何比较两个对象并获得它们差异的键值对?
【发布时间】:2016-01-18 21:16:45
【问题描述】:

我有两个对象:

1)

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

2)

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

如你所见:几乎相等,除了一件事:key B value 不同。

如何查看我的someNewArr 键值对差异?

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

我正在使用 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});
                }
            })
        });
    });

【问题讨论】:

  • 您发布的代码到底是什么不工作?我觉得很好。
  • 有没有可能得到{A: 10, B: 20, C: 30, D: 90}, {A: 10, B: 22, C: 30}的东西?
  • @K.Toress 是的,是的
  • 你能把你的输出改成diff = {changed: {B:22}, removed:null, created:{}}。我认为这将是一个很好的问题
  • @SheraliTurdiyev 是的

标签: javascript arrays angularjs


【解决方案1】:

递归差异

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

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

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

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 函数,它不关心给定的输入对象。 “左”对象可以包含“右”对象不包含的键,反之亦然,但我们仍然必须检测任一侧的变化。从高层次开始,这就是我们处理问题的方式

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

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
      )

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

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

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

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

合并

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

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
      )

这就是整个套件和一堆!展开下面的代码sn-p,在自己的浏览器中运行代码演示

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 完全分离,但tough nut to crack 本身是一个tough nut to crack。因为我们将关注点分离为单个函数,所以现在可以很容易地在程序的其他区域重用它们。我们想要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 =
  [ , , , , , 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 中支持数组更具挑战性

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

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

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

浅差异

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

递归交集

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

【讨论】:

  • 检查:console.log(diffObject({A: 10, B: 20, C: 30, D: 90}, {A: 10, B: 22, C: 30}));
  • @SheraliTurdiyev 您的评论已得到解决。 diffObject 现在将使用非对称密钥比较两个对象
  • 一切正常,但如果第一个 obj 中的 B 和第二个 obj 中的 B 也是数组呢?
  • @brabertaser1992 试试看,然后告诉我你对输出的理解。
  • @brabertaser1992 哈哈,我不是在开玩笑。我要求你努力理解为什么,而不是给你所有的答案。
【解决方案2】:

试试这个

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

【讨论】:

  • 解释它的作用和工作原理会很有帮助。这个答案的方法最终帮助了我。我需要比较的两个对象将具有相同的键,但值可能不同。我将const newProperties = newObjProperties.filter(prop =&gt; prevObjProperties.indexOf(prop) === -1); 更改为const newProperties = newObjProperties.filter(prop =&gt; prevObj[prop] !== newObj[prop] );
【解决方案3】:

解决方法很简单,

初始化你的数组,

var resultArray = [];

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

最后运行简单的代码

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

并在最后收集你的答案

console.log(resultArray);

【讨论】:

    【解决方案4】:

    我希望这会对你有所帮助。我用 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】:

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

      它需要 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 );
      

      希望对你有帮助。

      【讨论】:

        【解决方案6】:
        $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

        【讨论】:

          【解决方案7】:

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

          【讨论】:

          • 这可以用reduce大大简化
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-07-25
          • 1970-01-01
          • 2020-09-18
          • 2011-06-24
          • 2013-06-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多