如何将字符串二维数组转换为嵌套对象树结构

本文介绍在 next.js 13 管理后台开发中,如何将扁平的字符串二维数组(含父子关系标识)高效构建为具有 `children` 嵌套结构的对象数组,重点解决层级丢失与顺序依赖问题。

在构建树形菜单、分类导航或组织架构等场景中,后端常返回扁平化的层级数据(如 [parentId, id, name] 三元组),而前端需要将其转化为递归嵌套的对象结构。直接使用 map() 仅能做线性映射,无法建立父子引用关系;正确解法需借助哈希映射(Map)预存所有节点,并通过父 ID 动态挂载子节点。

以下是推荐的健壮实现:

function buildTreeFromFlatArray(data) {
  const nodeMap = new Map(); // 缓存所有节点:id → node 对象

  // 第一步:初始化所有节点(无论是否为根)
  for (const [parentId, id, name] of data) {
    nodeMap.set(id, {
      id: parseInt(id, 10),
      name,
      children: []
    });
  }

  // 第二步:建立父子关系(关键步骤)
  for (const [parentId, id, name] of data) {
    if (parentId !== "") {
      const parent = nodeMap.get(parentId);
      const child = nodeMap.get(id);
      if (parent && child) {
        parent.children.push(child);
      }
    }
  }

  // 第三步:提取所有根节点(ParentID 为空的项)
  const roots = [];
  for (const [parentId] of data) {
    if (parentId === "") {
      const root = nodeMap.get(data.find(row => row[0] === "")?.[1]);
      if (root && !roots.some(r => r.id === root.id)) {
        roots.push(root);
      }
    }
  }

  // 去重并确保顺序与原始根节点一致
  return data
    .filter(row => row[0] === "")
    .map(row => nodeMap.get(row[1]))
    .filter(Boolean);
}

// 使用示例
const input = [
  ["", "1", "Mobile Phones"],
  ["1", "2", "Apple"],
  ["1", "3", "Samsung"],
  ["", "4", "Tablets"],
  ["4", "5", "Huawei"],
  ["", "6", "X"],
  ["6", "7", "Y"],
  ["7", "8", "Z"]
];

console.log(buildTreeFromFlatArray(input));

关键要点说明:

  • Map 是核心:避免重复创建对象,确保同一 id 节点被唯一引用,父子挂载时修改的是同一内存实例;
  • 两遍遍历更安全:先建节点,再连关系,规避“父节点尚未创建”的潜在错误(即使输入顺序不保证,也可扩展为多轮收敛处理);
  • 根节点提取严谨:按原始数组中 parentId === "" 的顺序提取根节点,保持 UI 展示逻辑一致性;
  • 类型安全增强:使用 parseInt(id, 10) 显式转整型,防止 "01" 类字符串误解析;

⚠️ 注意事项:

  • 该算法默认要求父节点在子节点前声明(如 ["", "1", ...] 必须出现在 ["1", "2", ...] 之前)。若数据顺序不可控,需改用「拓扑排序」或「多轮扫描 + 待挂载队列

    」策略;
  • 若存在循环引用(如 A → B → A),需额外加入环检测逻辑;
  • 在 Next.js 服务端组件(Server Component)中使用时,确保数据已完整获取,避免在 useEffect 中异步调用导致 hydration 不一致。

此方案简洁、可读性强,适用于中等规模树(千级节点内),可直接集成至 Next.js 数据层或自定义 Hook 中,为动态菜单、权限路由等提供坚实的数据结构基础。