美文网首页
深度比对两个对象 获取其差异的键值对

深度比对两个对象 获取其差异的键值对

作者: Michael113c | 来源:发表于2021-07-13 11:06 被阅读0次

我有两个对象:

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

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

最佳答案

递归差异
大约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的 Angular 来看,我们输入对象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")
    ) 

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

在上面的示例中,使用程序其他区域的输出可能会更容易,因为标签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
      )

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

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本身是独立的。由于我们将关注点分为单个功能,因此现在可以很容易地在程序的其他区域中重用它们。我们想要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函数,用于比较具有相同键的对象并比较具有不同键的对象,但是没有一种解决方案对嵌套对象递归执行diff。
递归交点
this related Q&A中,我们采用两个输入对象并计算递归intersect而不是diff

相关文章

  • 深度比对两个对象 获取其差异的键值对

    我有两个对象: 如您所见:除了一件事之外,几乎相等:B的键值不同。 我如何才能了解someNewArr键值差异? ...

  • Redis数据操作(2)

    “ 哈希hash ” hash用于存储对象,对象的格式为键值对 1:设置 设置单个属性 设置多个属性 2:获取 获...

  • JavaScript:判断对象是否相等

    2022-01-10 周一 对比对象的每一个键值对:obj1.a === obj2.a Object.entrie...

  • Redis 字符串对象及其编码详解

    当我们在redis里面保存一个键值对的时候,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另外一个对象...

  • ArrayMap&&HashMap

    差异: 存储方式不同:ArrayMap内部使用两个数组,一个存HashCode,一个存键值对对象。HashMap内...

  • iOS 观察者KVO

    KVO 的基本概念(Key Value Observing) 基本概念键值观察是一种使对象获取其他对象的特定属性变...

  • iOS 面试知识点

    .1.数据的持久化:plist文件,对象归档,数据库,coredata 2.KVO:键值观察:是一种能使对象获取其...

  • OC中KVO的基本概念和使用方法

    基本概念 KVO(键值观察)是使用获取其他对象的特定属性变化的通知机制。控制器层的绑定技术就是严重依赖键值观察获得...

  • 循环遍历数组 对象的方法

    entries() 遍历键值对 keys() 遍历键名 values() 遍历键值 对象------遍历对象(属性...

  • 局部变量的作用域变大了,大到函数

    普通对象:键值对的一个集合函数对象:键值对的一个集合可执行带两个隐藏的属性(函数上下文\函数代码) 关于参数 ar...

网友评论

      本文标题:深度比对两个对象 获取其差异的键值对

      本文链接:https://www.haomeiwen.com/subject/qxgjpltx.html