You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将数据库文件结构分组为层级化JSON数组/对象?

从扁平文件记录构建层级化JSON结构

嘿,这个需求我太熟悉了!之前做文件管理系统的时候刚好遇到过一模一样的场景——从数据库拿的扁平数据要转成树形结构,还不能多次查库影响性能。结合你给出的排序规则和数据,我给你捋个清晰的实现思路和代码示例:


先明确现状和目标

你现在用这段Java代码生成了扁平的JSON数组:

// in this example, list is the returned data
JSONArray jList1 = new JSONArray();
for (int a = 0; a < list.size(); a++){
    Object[] fileObj = (Object[])list.get(a);
    String folder = (String)fileObj[0];
    String filename = (String)fileObj[1];
    JSONObject jObj1 = new JSONObject();
    jObj1.put("path",folder);
    jObj1.put("filename",filename);
    jList1.add(jObj1);
}
response.getWriter().write(jList1.toJSONString());

输出的扁平结构是这样的:

[
  { "path": "/a", "filename": "" },
  { "path": "/a", "filename": "file1.png" },
  { "path": "/a/b", "filename": "" },
  { "path": "/a/b", "filename": "file2.png" },
  { "path": "/a/b", "filename": "file3.png" },
  { "path": "/a/c", "filename": "" },
  { "path": "/a/c", "filename": "file4.jpg" },
  { "path": "/", "filename": "file5.jpg" },
  { "path": "/", "filename": "file6.jpg" }
]

而你想要的是层级化的树形结构:

[
  {
    "path": "/a",
    "filename": "",
    "files": [
      {
        "path": "/a/b",
        "filename": "",
        "files": [
          { "path": "/a/b", "filename": "file2.png" },
          { "path": "/a/b", "filename": "file3.png" }
        ]
      },
      {
        "path": "/a/c",
        "filename": "",
        "files": [
          { "path": "/a/c", "filename": "file4.jpg" }
        ]
      },
      { "path": "/a", "filename": "file1.png" }
    ]
  },
  { "path": "/", "filename": "file5.jpg" },
  { "path": "/", "filename": "file6.jpg" }
]

数据库的排序规则已经帮了大忙:

order by (CASE WHEN path='/' THEN 1 ELSE 0 END), path, filename

这个排序保证了同级目录下文件夹(filename为空)在前、文件在后,而且所有非根路径的条目都排在根路径前面,这为我们顺序构建树形结构提供了基础。


核心实现思路

我们要做的就是一次遍历扁平数组,用一个映射表快速定位父文件夹节点,逐步把文件/子文件夹挂载到对应的父节点下,具体步骤:

  1. 用HashMap做文件夹映射:创建一个Map<String, JSONObject>,键是文件夹的path,值是对应的JSON对象(包含files数组)。这样我们可以O(1)时间找到任意文件夹的节点,不用递归查找,性能拉满。
  2. 顺序处理每个条目:
    • 如果是文件夹(filename为空):
      • 创建带files数组的JSON对象,存入映射表。
      • 找出它的父路径(比如/a/b的父路径是/a,根路径/没有父节点)。如果父文件夹在映射表里存在,就把当前文件夹加到父节点的files里;如果不存在(比如根目录的子文件夹,而根目录的文件夹还没被处理),就先把它作为根节点加入最终结果数组。
    • 如果是文件(filename不为空):
      • 创建对应的JSON对象。
      • 直接从映射表里找它所在的文件夹(就是它的path字段),找到的话就把文件加到该文件夹的files数组里;找不到的话(比如根目录下的文件,根目录没有文件夹条目),就直接加到最终结果数组里。
  3. 收尾处理:因为排序规则的原因,根目录的文件夹条目会最后处理,这时候如果之前有子文件夹被当成根节点,我们可以把它们移到根文件夹的files里(不过看你的示例,根目录没有文件夹条目,所以这一步可能不需要)。

具体代码实现(基于org.json库)

// 假设list是数据库查询返回的结果集合
JSONArray hierarchicalTree = new JSONArray();
// 文件夹映射表:key=文件夹path,value=对应的JSON节点(带files数组)
Map<String, JSONObject> folderNodeMap = new HashMap<>();

for (int i = 0; i < list.size(); i++) {
    Object[] fileRecord = (Object[]) list.get(i);
    String path = (String) fileRecord[0];
    String filename = (String) fileRecord[1];
    JSONObject currentNode = new JSONObject();
    currentNode.put("path", path);
    currentNode.put("filename", filename);

    if (filename.isEmpty()) {
        // 处理文件夹节点
        currentNode.put("files", new JSONArray());
        folderNodeMap.put(path, currentNode);

        String parentPath = getParentFolderPath(path);
        if (parentPath != null) {
            JSONObject parentFolder = folderNodeMap.get(parentPath);
            if (parentFolder != null) {
                // 父文件夹存在,挂载到父节点下
                parentFolder.getJSONArray("files").put(currentNode);
            } else {
                // 父文件夹还未处理(比如根目录的子文件夹),先作为根节点加入结果
                hierarchicalTree.put(currentNode);
            }
        } else {
            // 根目录文件夹,直接加入结果
            hierarchicalTree.put(currentNode);
        }
    } else {
        // 处理文件节点
        JSONObject parentFolder = folderNodeMap.get(path);
        if (parentFolder != null) {
            // 找到所属文件夹,挂载进去
            parentFolder.getJSONArray("files").put(currentNode);
        } else {
            // 没有对应的文件夹节点(比如根目录下的文件),直接加入结果
            hierarchicalTree.put(currentNode);
        }
    }
}

// 额外处理:如果根目录文件夹存在,把之前误加入根结果的子文件夹移进去
JSONObject rootFolder = folderNodeMap.get("/");
if (rootFolder != null) {
    // 遍历根结果数组,把属于根目录的子文件夹移到rootFolder的files里
    JSONArray tempRoot = new JSONArray();
    for (int i = 0; i < hierarchicalTree.length(); i++) {
        JSONObject node = hierarchicalTree.getJSONObject(i);
        if (node.getString("filename").isEmpty() && getParentFolderPath(node.getString("path")).equals("/")) {
            rootFolder.getJSONArray("files").put(node);
        } else {
            tempRoot.put(node);
        }
    }
    // 把根目录文件夹加入临时数组,替换原结果
    tempRoot.put(rootFolder);
    hierarchicalTree = tempRoot;
}

response.getWriter().write(hierarchicalTree.toJSONString());

辅助方法getParentFolderPath:

private String getParentFolderPath(String currentPath) {
    if ("/".equals(currentPath)) {
        return null; // 根目录没有父文件夹
    }
    int lastSlashPos = currentPath.lastIndexOf('/');
    return lastSlashPos == 0 ? "/" : currentPath.substring(0, lastSlashPos);
}

为什么这个方案可行?

  • 利用现有排序:数据库的排序保证了我们处理文件夹的时候,除了根目录的文件夹,其他父文件夹都已经被处理并存入映射表了,避免了找不到父节点的问题。
  • 一次遍历搞定:全程只遍历一次查询结果,没有额外的数据库查询,性能最优。
  • 兼容各种场景:不管树形结构有多少层级,也不管根目录有没有文件夹条目,都能正确生成你想要的层级结构。

内容的提问来源于stack exchange,提问作者fanfavorite

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:18:39