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

IT技术 javascript arrays angularjs
2021-02-04 11:03:39

我有两个对象:

1)

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

2)

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

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

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

喜欢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});
                }
            })
        });
    });
6个回答

递归差异

将近 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,我们的输入对象a之间的关系b可以是完全任意的。为了传达哪个对象造成差异,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")
    ) 

差异1

我们采用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' } }

在上面的示例中,在程序的其他区域处理输出可能更容易,因为标签originalmodifiedleft更具描述性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

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
      )

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

评论

当我们回顾我们的diff功能时,我想强调其设计的一个重要部分。大部分工作由与merge完全分离函数处理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 =
  [ , , , , , 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 } ] }

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

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

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

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

浅差异

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

递归交集

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

@brabertaser1992 哈哈,我不是在开玩笑。我要求你花一些精力去理解为什么而不是给你所有的答案。
2021-03-16 11:03:39
@brabertaser1992 试试看,然后告诉我你对输出的理解。
2021-03-18 11:03:39
检查: console.log(diffObject({A: 10, B: 20, C: 30, D: 90}, {A: 10, B: 22, C: 30}));
2021-03-19 11:03:39
@SheraliTurdiyev 您的评论已得到解决。diffObject现在将比较具有非对称密钥的两个对象
2021-03-25 11:03:39
一切都很好,但是如果第一个 obj 中的 B 和第二个 obj 中的 B 也是数组呢?
2021-04-11 11:03:39

解决方法很简单,

初始化你的数组,

var resultArray = [];

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

最后运行简单的代码

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

并在最后收集你的答案

console.log(resultArray);

试试这个

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 => prevObjProperties.indexOf(prop) === -1);const newProperties = newObjProperties.filter(prop => prevObj[prop] !== newObj[prop] );
2021-03-19 11:03:39

这将返回第一个参数相对于第二个参数的差异。不过,我没有在这里使用 angular.forEach。

这可以大大简化 reduce
2021-04-07 11:03:39
$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);

这是演示