比较两个嵌套数据结构时如何识别更改的属性值? [已关闭]

问题描述 投票:0回答:3

我正在开发一个 TypeScript 项目,我需要比较两个具有潜在嵌套结构的对象,并识别它们之间已更改的字段。例如,考虑一个旧对象 oldObject,其中包含姓名、年龄、城市、朋友(数组)等字段,以及其中可能嵌套的对象。现在,我有一个具有相同结构的新对象 newObject,但某些字段的值可能会更新。

这是一个例子:

const oldObject = { name: 'John', age: 30, city: 'New York', friends: ['ali', 'qasim'], def: { 'a': 1, 'b': 2 } };
const newObject = { name: 'John', age: 35, city: 'New York', friends: ['ali', 'haider'] };

在这种情况下,我需要能够识别并提取 oldObject 和 newObject 之间更改的字段及其新值,同时保留嵌套结构。

我尝试了比较函数,但它无法正确处理嵌套结构和数组。如何在 TypeScript 中实现一个解决方案,准确识别更改的字段,同时保留对象的嵌套结构?

  • 我正在寻找一种 TypeScript 函数或方法,可以准确识别具有嵌套结构的两个对象之间更改的字段。
  • 该函数应正确处理嵌套对象和数组,识别其中的更改。
  • 该解决方案对于不同大小和复杂性的对象应该是高效且可扩展的。
  • 对所提供的示例功能的任何见解或改进将不胜感激。
  • 清晰的解释和示例将有助于更好地理解和实施。
javascript arrays object data-structures difference
3个回答
1
投票

解决 OP 问题的可行方法可能是将任何给定的数据结构序列化为基于键路径的映射/索引。

在比较和区分两个数据结构时,首先会创建每个结构的基于

Map
的序列化变体。

然后,人们可以轻松地为每个创建的映射创建一个

Set
实例,该实例仅保存每个相关的序列化数据结构的键路径字符串。

通过使用新的

Set
方法
intersection
difference
(利用后两者的 js 核心实现),可以检索特定的键路径字符串集,例如重叠的键路径和唯一的键路径每个数据结构。

达到这一点后,从所有创建的地图和集合中创建一个

diff
对象非常容易,这是......

  • 计算一个

    changed
    差异条目,指出一个相同的键路径上的不同值。

  • 创建一个

    deleted
    差异条目,其特征是基于键路径的键值对,这些键值对对于所提供的最近的数据对象是唯一的。

  • 创建一个

    added
    差异条目,其特征是基于键路径的键值对,这些键值对对于所提供的当前数据对象是唯一的。

const oldObject = {
  name: 'John', age: 30,
  friends: ['ali', 'qasim', { foo: 'Foo', 'foo bar': { baz: 'Baz', biz: 'Biz' } }],
};
const newObject = {
  name: 'John', age: 35, city: 'New York',
  friends: ['ali', 'haider', { 'foo bar': { baz: 'BAZ'}, foo: 'Foo' }],
};

const diff = diffDataValuesByKeypath(oldObject, newObject);

console.log({ oldObject, newObject, diff });
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/3.36.0/minified.js"></script>

<script>
function concatKeypath(keypath, key) {

  // - test for a key which can be used with dot chaining.
  // - see ... [https://regex101.com/r/tew8gr/1]
  return (/^[_$\p{L}][_$\p{L}\p{N}]*$/u).test(key)

    // - concat a dot chained key.
    ? (!!keypath && `${ keypath }.${ key }` || key)

    // - concat a quoted key with square bracket notation.
    : `${ keypath }["${ key }"]`;
}

function mapDataStructure(value, keypath = '', map = new Map) {
  if (Array.isArray(value)) {
    value
      .forEach((item, idx) =>

        mapDataStructure(item, `${ keypath }[${ idx }]`, map)
      );
  } else if (value && typeof value === 'object') {
    Object
      .entries(value)
      .forEach(([key, val]) =>

        mapDataStructure(val, concatKeypath(keypath, key), map)
      );
  } else {
    map.set(keypath, value);
  }
  return map;
}

function diffDataValuesByKeypath(recentData, currentData) {
  const recentMap = mapDataStructure(recentData);
  const currentMap = mapDataStructure(currentData);

  const recentPaths = new Set([...recentMap.keys()]);
  const currentPaths = new Set([...currentMap.keys()]);

  const collidingPaths = recentPaths.intersection(currentPaths);
  const uniqueRecentPaths = recentPaths.difference(currentPaths);
  const uniqueCurrentPaths = currentPaths.difference(recentPaths);

  return {

    changed: [...collidingPaths.values()]
      .reduce((result, keypath) => {

        const recentValue = recentMap.get(keypath);
        const currentValue = currentMap.get(keypath);

        if (!Object.is(recentValue, currentValue)) {

          result[keypath] = {
            recent: recentValue,
            current: currentValue,
          };
        }
        return result;

      }, {}),

    deleted: [...uniqueRecentPaths.values()]
      .reduce((result, keypath) => {

        result[keypath] = recentMap.get(keypath);
        return result;

      }, {}),

    added: [...uniqueCurrentPaths.values()]
      .reduce((result, keypath) => {

        result[keypath] = currentMap.get(keypath);
        return result;

      }, {}),
  };
}
</script>


-3
投票

const oldObject = { name: 'John', age: 30, city: 'New York', friends: ['ali', 'qasim'], def: { 'a': 1, 'b': 2 } };

const newObject = { name: 'John', age: 35, city: 'New York', friends: ['ali', 'haider'] };
let oldobj = Object.keys(oldObject)
console.log(oldobj)
let newobj = Object.keys(newObject)
console.log(newobj)
let result = oldobj.filter(x => !newobj.includes(x));

if(result.length> 0){
    console.log(oldObject.def)
}

// 在 2 个数组中查找键。并检查两个区域中的密钥是否相同


-4
投票

要准确识别和比较具有嵌套结构的 TypeScript 对象中更改的字段,您可以使用递归方法。以下是如何实现一个函数来实现此目的:

  function findChangedFields(oldObj: any, newObj: any): { old: any, new: any } {
const changedFields: { old: any, new: any } = { old: {}, new: {} };

for (const key in oldObj) {
    if (oldObj.hasOwnProperty(key)) {
        if (!newObj.hasOwnProperty(key)) {
            changedFields.old[key] = oldObj[key];
            changedFields.new[key] = undefined; // Field does not exist in new object
        } else if (Array.isArray(oldObj[key]) && Array.isArray(newObj[key])) {
            if (JSON.stringify(oldObj[key]) !== JSON.stringify(newObj[key])) {
                changedFields.old[key] = oldObj[key];
                changedFields.new[key] = newObj[key];
            }
        } else if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
            const nestedChanges = findChangedFields(oldObj[key], newObj[key]);
            if (Object.keys(nestedChanges.old).length > 0 || Object.keys(nestedChanges.new).length > 0) {
                changedFields.old[key] = nestedChanges.old;
                changedFields.new[key] = nestedChanges.new;
            }
        } else {
            if (oldObj[key] !== newObj[key]) {
                changedFields.old[key] = oldObj[key];
                changedFields.new[key] = newObj[key];
            }
        }
    }
}

// Check for new keys in newObj
for (const key in newObj) {
    if (newObj.hasOwnProperty(key) && !oldObj.hasOwnProperty(key)) {
        changedFields.old[key] = undefined; // Field does not exist in old object
        changedFields.new[key] = newObj[key];
    }
}

return changedFields;
}

// Example usage:
const oldObject = { name: 'John', age: 30, city: 'New York', friends: ['ali', 'qasim'], def: { 'a': 1, 'b': 3 }, ng: { a: { d: 1 } } };
const newObject = { name: 'John', age: 35, city: 'New York', friends: ['ali', 'haider'], def: {}, ng: { a: {} } };

const changedFields = findChangedFields(oldObject, newObject);
console.log(changedFields);

该函数递归遍历对象和数组来比较它们的元素。如果发现差异,它将在changedFields对象中记录更改的字段及其新值。

您可以使用示例对象 oldObject 和 newObject 测试此函数,以查看更改的字段及其新值。

© www.soinside.com 2019 - 2024. All rights reserved.