将平面对象数组转换为嵌套对象 [英] Convert array of flat objects to nested objects
问题描述
我有以下数组(实际上是来自后端服务):
I have the following array (that's actually coming from a backend service):
const flat: Item[] = [
{ id: 'a', name: 'Root 1', parentId: null },
{ id: 'b', name: 'Root 2', parentId: null },
{ id: 'c', name: 'Root 3', parentId: null },
{ id: 'a1', name: 'Item 1', parentId: 'a' },
{ id: 'a2', name: 'Item 1', parentId: 'a' },
{ id: 'b1', name: 'Item 1', parentId: 'b' },
{ id: 'b2', name: 'Item 2', parentId: 'b' },
{ id: 'b2-1', name: 'Item 2-1', parentId: 'b2' },
{ id: 'b2-2', name: 'Item 2-2', parentId: 'b2' },
{ id: 'b3', name: 'Item 3', parentId: 'b' },
{ id: 'c1', name: 'Item 1', parentId: 'c' },
{ id: 'c2', name: 'Item 2', parentId: 'c' }
];
其中Item
是:
interface Item {
id: string;
name: string;
parentId: string;
};
为了与显示树(类似于文件夹)视图的组件兼容,需要将其转换为:
In order to be compatible with a component that displays a tree (folder like) view, it needs to be transformed into:
const treeData: NestedItem[] = [
{
id: 'a',
name: 'Root 1',
root: true,
count: 2,
children: [
{
id: 'a1',
name: 'Item 1'
},
{
id: 'a2',
name: 'Item 2'
}
]
},
{
id: 'b',
name: 'Root 2',
root: true,
count: 5, // number of all children (direct + children of children)
children: [
{
id: 'b1',
name: 'Item 1'
},
{
id: 'b2',
name: 'Item 2',
count: 2,
children: [
{ id: 'b2-1', name: 'Item 2-1' },
{ id: 'b2-2', name: 'Item 2-2' },
]
},
{
id: 'b3',
name: 'Item 3'
},
]
},
{
id: 'c',
name: 'Root 3',
root: true,
count: 2,
children: [
{
id: 'c1',
name: 'Item 1'
},
{
id: 'c2',
name: 'Item 2'
}
]
}
];
其中NestedItem
是:
interface NestedItem {
id: string;
name: string;
root?: boolean;
count?: number;
children?: NestedItem[];
}
到目前为止,我所尝试的一切都是这样的:
All I've tried so far is something like:
// Get roots first
const roots: NestedItem[] = flat
.filter(item => !item.parentId)
.map((item): NestedItem => {
return { id: item.id, name: item.name, root: true }
});
// Add "children" to those roots
const treeData = roots.map(node => {
const children = flat
.filter(item => item.parentId === node.id)
.map(item => {
return { id: item.id, name: item.name }
});
return {
...node,
children,
count: node.count ? node.count + children.length : children.length
}
});
但是,这当然只会获得第一级子级(根节点的直接子级).它某种程度上需要递归,但是我不知道如何实现.
But this only gets the first level of children, of course (direct children of root nodes). It somehow needs to be recursive, but I have no idea how to accomplish that.
推荐答案
不对展平数组的顺序或嵌套对象的深度进行任何假设:
Array.prototype.reduce
具有足够的灵活性来完成此操作.如果您不熟悉Array.prototype.reduce
,我建议阅读此内容.您可以通过执行以下操作来完成此操作.
Array.prototype.reduce
is flexible enough to get this done. If you are not familiar with Array.prototype.reduce
I recommend reading this. You could accomplish this by doing the following.
我这里有两个依赖递归的函数:findParent
和checkLeftOvers
. findParent
尝试查找父对象,并根据是否找到对象返回true
或false
.在我的reducer中,如果findParent
返回false
,则将当前值添加到剩余数组中.如果findParent
返回true
,我调用checkLeftOvers
来查看我剩余数组中的任何对象是否是刚刚添加的对象findParent
的子对象.
I have two functions that rely on recursion here: findParent
and checkLeftOvers
. findParent
attempts to find the objects parent and returns true
or false
based on whether it finds it. In my reducer I add the current value to the array of left overs if findParent
returns false
. If findParent
returns true
I call checkLeftOvers
to see if any object in my array of left overs is the child of the object findParent
just added.
注意:我在flat
数组中添加了{ id: 'b2-2-1', name: 'Item 2-2-1', parentId: 'b2-2'}
,以演示它会深入到您想要的深度.我还对flat
进行了重新排序,以证明在这种情况下也可以使用.希望这会有所帮助.
Note: I added { id: 'b2-2-1', name: 'Item 2-2-1', parentId: 'b2-2'}
to the flat
array to demonstrate that this will go as deep as you'd like. I also reordered flat
to demonstrate that this will work in that case as well. Hope this helps.
const flat = [
{ id: 'a2', name: 'Item 1', parentId: 'a' },
{ id: 'b2-2-1', name: 'Item 2-2-1', parentId: 'b2-2'},
{ id: 'a1', name: 'Item 1', parentId: 'a' },
{ id: 'a', name: 'Root 1', parentId: null },
{ id: 'b', name: 'Root 2', parentId: null },
{ id: 'c', name: 'Root 3', parentId: null },
{ id: 'b1', name: 'Item 1', parentId: 'b' },
{ id: 'b2', name: 'Item 2', parentId: 'b' },
{ id: 'b2-1', name: 'Item 2-1', parentId: 'b2' },
{ id: 'b2-2', name: 'Item 2-2', parentId: 'b2' },
{ id: 'b3', name: 'Item 3', parentId: 'b' },
{ id: 'c1', name: 'Item 1', parentId: 'c' },
{ id: 'c2', name: 'Item 2', parentId: 'c' }
];
function checkLeftOvers(leftOvers, possibleParent){
for (let i = 0; i < leftOvers.length; i++) {
if(leftOvers[i].parentId === possibleParent.id) {
delete leftOvers[i].parentId
possibleParent.children ? possibleParent.children.push(leftOvers[i]) : possibleParent.children = [leftOvers[i]]
possibleParent.count = possibleParent.children.length
const addedObj = leftOvers.splice(i, 1)
checkLeftOvers(leftOvers, addedObj[0])
}
}
}
function findParent(possibleParents, possibleChild) {
let found = false
for (let i = 0; i < possibleParents.length; i++) {
if(possibleParents[i].id === possibleChild.parentId) {
found = true
delete possibleChild.parentId
if(possibleParents[i].children) possibleParents[i].children.push(possibleChild)
else possibleParents[i].children = [possibleChild]
possibleParents[i].count = possibleParents[i].children.length
return true
} else if (possibleParents[i].children) found = findParent(possibleParents[i].children, possibleChild)
}
return found;
}
const nested = flat.reduce((initial, value, index, original) => {
if (value.parentId === null) {
if (initial.left.length) checkLeftOvers(initial.left, value)
delete value.parentId
value.root = true;
initial.nested.push(value)
}
else {
let parentFound = findParent(initial.nested, value)
if (parentFound) checkLeftOvers(initial.left, value)
else initial.left.push(value)
}
return index < original.length - 1 ? initial : initial.nested
}, {nested: [], left: []})
console.log(nested)
这篇关于将平面对象数组转换为嵌套对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!