如何以最有效的方式完全平整(映射/减少/递归)未知深度的嵌套对象?

How to entirely level (map / reduce / recursion) a nested object of unknown depth in the most efficient manner?

我想以更大规模、更高效的方式做类似下面的事情。假设我有一个对象数组,其中每个对象都需要被拉平/展平。

像这样转换...

[{
  name: 'John Doe',
  address: {
    apartment: 1550,
    streetno: 167,
    streetname: 'Victoria',
  },
}, {
  name: 'Joe Smith',
  address: {
    apartment: 2,
    streetno: 111,
    streetname: 'Jones',
  },
}]

...到那个...

[{
  name: 'John Doe',
  apartment: 1550,
  streetno: 167,
  streetname: 'Victoria',
}, {
  name: 'Joe Smith',
  apartment: 2,
  streetno: 111,
  streetname: 'Jones',
}]

如上所示,address也是一个需要leveled/flattened的对象。

但最重要的是,人们事先并不知道object/data-structure。因此,人们既不知道 属性 名称,也不知道嵌套级别的深度。

那不是数组。

如果你想扁平化一个字典,这样做:

这具有 O(n^k) 和 Omega(n) 的时间复杂度,其中 n 是字典的大小,k 是字典的深度(或字典中有多少个嵌套)。

假设您有这些对象的数组,您可以使用解构轻松地将每个对象与其“地​​址”组合起来属性:

const myInput = [
  {
    name: 'john doe',
    address: { apartment: 1550, streetno: 167, streetname: 'Victoria'}
  },
  {
    name: 'Joe Smith',
    address: { apartment: 2, streetno: 111, streetname: 'Jones'}
  }
];

const myOutput = myInput.map(({address, ...rest}) => ({...rest, ...address}));
console.log(myOutput);

map over the array and return a new object that has had its address property merged into it,地址属性删除,新对象返回。

const arr=[{name:"john doe",address:{apartment:1550,streetno:167,streetname:"Victoria",a:"a"},b:"b"}];

const out = arr.map(obj => {
  const newObj = { ...obj, ...obj.address };
  delete newObj.address;
  return newObj;
});

console.log(out);

"So before receiving the object you do not know much about its structure."

OP的主要任务实际上是将任何给定的嵌套object-baseddata-structure平到一个对象中只有一个 entries-level。而且由于事先对 data-structure 一无所知,因此必须想出一种递归方法。

一旦实现,这样的 cause 函数可以用作数组 mapping 过程的回调。

递归实现本身基于type-detection(区分Array-和Object-类型和原始值)和reduceing the entries(key-value 对)根据当前处理的 value 类型的对象。

function recursivelyLevelObjectEntriesOnly(type) {
  let result = type;
  if (Array.isArray(type)) {

    result = type
      .map(recursivelyLevelObjectEntriesOnly);

  } else if (type && 'object' === typeof type) {

    result = Object
      .entries(type)
      .reduce((merger, [key, value]) => {

        if (value && 'object' === typeof value && !Array.isArray(value)) {

          Object.assign(merger, recursivelyLevelObjectEntriesOnly(value));
        } else {
          merger[key] = recursivelyLevelObjectEntriesOnly(value);
        }
        return merger;

      }, {});    
  }
  return result;
}

const sampleData = [{
  name: 'John Doe',
  address: { apartment: 1550, streetno: 167, streetname: 'Victoria' },
}, {
  name: 'Joe Smith',
  address: { apartment: 2, streetno: 111, streetname: 'Jones' },
}, {
  foo: {
    bar: "bar",
    baz: "baz",
    biz: {
      buzz: "buzz",
      bizz: [{
        name: 'John Doe',
        address: { apartment: 1550, streetno: 167, streetname: 'Victoria' },
      }, {
        name: 'Joe Smith',
        address: { apartment: 2, streetno: 111, streetname: 'Jones' },
      }, {
        foo: {
          bar: "bar",
          baz: "baz",
          biz: {
            buzz: "buzz",
            booz: {
              foo: "foo",
            },
          },
        },
      }],
      booz: {
        foo: "foo",
      },
    },
  },
}];

const leveledObjectData = sampleData.map(recursivelyLevelObjectEntriesOnly);
console.log({ leveledObjectData });

// no mutation at `sampleData`.
console.log({ sampleData });
.as-console-wrapper { min-height: 100%!important; top: 0; }

这是一种基于递归函数的相当简单的方法,它首先将元素转换为以下形式,然后对结果调用 Object .fromEntries

[
  ["name", "John Doe"],
  ["apartment", 1550],
  ["streetno", 167],
  ["streetname", "Victoria"]
]

看起来像这样:

const deepEntries = (o) =>
  Object .entries (o) .flatMap (([k, v]) => v.constructor === Object ? deepEntries (v) : [[k, v]])

const deepFlat = (o) =>
  Object .fromEntries (deepEntries (o))

const deepFlatAll = (xs) =>
  xs .map (deepFlat)

const input = [{name: 'John Doe', address: {apartment: 1550, streetno: 167, streetname: 'Victoria'}, }, {name: 'Joe Smith', address: {apartment: 2, streetno: 111, streetname: 'Jones'}}]

console .log (deepFlatAll (input))
.as-console-wrapper {max-height: 100% !important; top: 0}

但是您在这里确实有一个可能的重大问题。如果您要展平多个级别,则很可能不同级别的节点具有相同的名称;当您将对象放回原处时,其中大部分都会被破坏。

此类问题的一种解决方案是将其扁平化为不同的格式。我已经看到类似这样的东西使用得非常成功:

{
  "name": "John Doe",
  "address.apartment": 1550,
  "address.streetno": 167,
  "address.streetname": "Victoria"
}

如果你看看 Whosebug,你肯定能找到如何做到这一点的答案。