如何以最有效的方式完全平整(映射/减少/递归)未知深度的嵌套对象?
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 函数可以用作数组 map
ping 过程的回调。
递归实现本身基于type-detection(区分Array
-和Object
-类型和原始值)和reduce
ing 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,你肯定能找到如何做到这一点的答案。
我想以更大规模、更高效的方式做类似下面的事情。假设我有一个对象数组,其中每个对象都需要被拉平/展平。
像这样转换...
[{
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 函数可以用作数组 map
ping 过程的回调。
递归实现本身基于type-detection(区分Array
-和Object
-类型和原始值)和reduce
ing 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,你肯定能找到如何做到这一点的答案。