首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将平面对象数组转换为嵌套对象

将平面对象数组转换为嵌套对象
EN

Stack Overflow用户
提问于 2019-02-06 15:44:04
回答 8查看 4.1K关注 0票数 4

我有以下数组(实际上来自后端服务):

代码语言:javascript
复制
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是:

代码语言:javascript
复制
interface Item {
    id: string;
    name: string;
    parentId: string;
};

为了与显示树(类似文件夹)视图的组件兼容,需要将其转换为:

代码语言:javascript
复制
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是:

代码语言:javascript
复制
interface NestedItem {
    id: string;
    name: string;
    root?: boolean;
    count?: number;
    children?: NestedItem[];
}

到目前为止,我所做的就是:

代码语言:javascript
复制
// 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
    }
});

当然,这只能获得第一级子级(根节点的直接子级)。它需要某种程度的递归,但我不知道如何做到这一点。

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2019-02-06 16:35:10

不对扁平数组的顺序或嵌套对象的深度进行任何假设:

Array.prototype.reduce具有足够的灵活性来完成这一任务。如果您不熟悉Array.prototype.reduce,我推荐读这个。您可以通过执行以下操作来完成这一任务。

这里有两个依赖递归的函数:findParentcheckLeftOversfindParent尝试查找对象父对象,并根据是否找到对象返回truefalse。在我的还原器中,如果findParent返回false,我会将当前值添加到剩余数组中。如果findParent返回true,我调用checkLeftOvers来查看我的左数组中的任何对象是否是findParent刚刚添加的对象的子对象。

注意:我将{ id: 'b2-2-1', name: 'Item 2-2-1', parentId: 'b2-2'}添加到flat数组中,以演示这将达到您想要的深度。我还对flat进行了重新排序,以说明这种方法在这种情况下也是可行的。希望这能有所帮助。

代码语言:javascript
复制
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)

票数 4
EN

Stack Overflow用户

发布于 2020-02-29 18:47:04

您可以为一棵树提供一种标准的方法,该方法采用单个循环,并存储子和父之间以及父与子之间的关系。

对于根属性,您需要额外的检查。

然后采用迭代和递归的方法来获得计数。

代码语言:javascript
复制
var data = [{ 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: 'b3', name: 'Item 3', parentId: 'b' }, { id: 'c1', name: 'Item 1', parentId: 'c' }, { id: 'c2', name: 'Item 2', parentId: 'c' }, { id: 'b2-1', name: 'Item 2-1', parentId: 'b2' }, { id: 'b2-2', name: 'Item 2-2', parentId: 'b2' },],
    tree = function (data, root) {

        function setCount(object) {
            return object.children
                ? (object.count = object.children.reduce((s, o) => s + 1 + setCount(o), 0))
                : 0;
        }                

        var t = {};
        data.forEach(o => {
            Object.assign(t[o.id] = t[o.id] || {}, o);
            t[o.parentId] = t[o.parentId] || {};
            t[o.parentId].children = t[o.parentId].children || [];
            t[o.parentId].children.push(t[o.id]);
            if (o.parentId === root) t[o.id].root = true;          // extra
        });

        setCount(t[root]);                                         // extra
        return t[root].children;
    }(data, null);

console.log(tree);
代码语言:javascript
复制
.as-console-wrapper { max-height: 100% !important; top: 0; }

票数 2
EN

Stack Overflow用户

发布于 2019-02-06 16:51:06

假设平面项数组总是按照您的情况排序(父节点在子节点之前排序)。下面的代码应该可以完成这项工作。

首先,我在没有count属性的情况下构建树,使用数组上的reduce来构建一个映射,以跟踪每个节点,并将父节点链接到子节点:

代码语言:javascript
复制
type NestedItemMap = { [nodeId: string]: NestedItem };

let nestedItemMap: NestedItemMap = flat
    .reduce((nestedItemMap: NestedItemMap, item: Item): NestedItemMap => {

        // Create the nested item
        nestedItemMap[item.id] = {
            id: item.id,
            name: item.name
        }

        if(item.parentId == null){
            // No parent id, it's a root node
            nestedItemMap[item.id].root = true;
        }
        else{
            // Child node
            let parentItem: NestedItem = nestedItemMap[item.parentId];

            if(parentItem.children == undefined){
                // First child, create the children array
                parentItem.children = [];
                parentItem.count = 0;

            }

            // Add the child node in it's parent children
            parentItem.children.push(
                nestedItemMap[item.id]
            );
            parentItem.count++;
        }

        return nestedItemMap;
    }, {});

在减少数组时,父节点始终是第一位的,这一事实确保在构建子节点时父节点在nestedItemMap中是可用的。

这里有树,但没有count属性:

代码语言:javascript
复制
let roots: NestedItem[] = Object.keys(nestedItemMap)
    .map((key: string): NestedItem => nestedItemMap[key])
    .filter((item: NestedItem): boolean => item.root);

要填充count属性,我个人更喜欢在树上执行后续深度优先搜索。但是在您的例子中,由于节点id名称(排序,父节点id排在第一位)。您可以使用以下方法计算它们:

代码语言:javascript
复制
let roots: NestedItem[] = Object.keys(nestedItemMap)
    .map((key: string): NestedItem => nestedItemMap[key])
    .reverse()
    .map((item: NestedItem): NestedItem => {
        if(item.children != undefined){
            item.count = item.children
                .map((child: NestedItem): number => {
                    return 1 + (child.count != undefined ? child.count : 0);
                })
                .reduce((a, b) => a + b, 0);
        }

        return item;
    })
    .filter((item: NestedItem): boolean => item.root)
    .reverse();

我只需反转数组,首先获取所有的子级(类似于后期订单DFS),并计算count值。最后一个相反的情况是在这里进行排序,就像在您的问题:)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54557403

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档