Java从可变深度JSON中提取各子树ID列表的实现方案
基于Jackson提取JSON树的ID路径列表
递归方案(修改原有遍历方法)
假设你的JSON节点结构包含id字段和children子节点数组,只需在原有递归遍历逻辑中加入路径回溯的逻辑,即可收集所有根到节点的路径(支持叶子节点或所有节点)。
提取根到叶子节点的路径
如果只需要叶子节点的完整路径(如示例中的1,3,6),修改后的递归代码如下:
import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; import java.util.List; public class JsonTreePathExtractor { public List<List<Integer>> getLeafIdPaths(JsonNode rootNode) { List<List<Integer>> paths = new ArrayList<>(); if (rootNode == null || !rootNode.has("id")) return paths; traverseLeafNodes(rootNode, new ArrayList<>(), paths); return paths; } private void traverseLeafNodes(JsonNode currentNode, List<Integer> currentPath, List<List<Integer>> result) { // 将当前节点ID加入路径 int currentId = currentNode.get("id").asInt(); currentPath.add(currentId); JsonNode children = currentNode.get("children"); // 判断是否为叶子节点(无有效子节点) if (children == null || !children.isArray() || children.size() == 0) { // 保存路径副本,避免后续修改影响结果 result.add(new ArrayList<>(currentPath)); } else { // 递归遍历所有子节点 for (JsonNode child : children) { traverseLeafNodes(child, currentPath, result); } } // 回溯:移除当前节点ID,保证递归返回后路径状态正确 currentPath.remove(currentPath.size() - 1); } }
提取所有节点的完整路径
如果需要收集每一个节点的根到自身的路径(比如根节点1、子节点1,3等),只需调整逻辑,在加入当前节点ID后立即保存路径:
private void traverseAllNodes(JsonNode currentNode, List<Integer> currentPath, List<List<Integer>> result) { int currentId = currentNode.get("id").asInt(); currentPath.add(currentId); // 保存当前节点的路径 result.add(new ArrayList<>(currentPath)); JsonNode children = currentNode.get("children"); if (children != null && children.isArray() && children.size() > 0) { for (JsonNode child : children) { traverseAllNodes(child, currentPath, result); } } currentPath.remove(currentPath.size() - 1); }
非递归方案(避免深度过大栈溢出)
如果你的JSON树深度极大,递归可能导致栈溢出,这时可以用栈模拟递归过程,通过创建路径副本来替代回溯:
import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; import java.util.List; import java.util.Stack; public class JsonTreePathExtractor { public List<List<Integer>> getLeafIdPathsNonRecursive(JsonNode rootNode) { List<List<Integer>> paths = new ArrayList<>(); if (rootNode == null || !rootNode.has("id")) return paths; // 栈中存储(当前节点,当前路径) Stack<Object[]> nodeStack = new Stack<>(); nodeStack.push(new Object[]{rootNode, new ArrayList<Integer>()}); while (!nodeStack.isEmpty()) { Object[] entry = nodeStack.pop(); JsonNode currentNode = (JsonNode) entry[0]; List<Integer> parentPath = (List<Integer>) entry[1]; // 创建当前节点的路径副本 List<Integer> currentPath = new ArrayList<>(parentPath); currentPath.add(currentNode.get("id").asInt()); JsonNode children = currentNode.get("children"); if (children == null || !children.isArray() || children.size() == 0) { paths.add(currentPath); } else { // 栈是后进先出,倒序压入子节点保证遍历顺序和递归一致 for (int i = children.size() - 1; i >= 0; i--) { nodeStack.push(new Object[]{children.get(i), currentPath}); } } } return paths; } }
适配调整说明
- ID类型适配:如果ID是字符串而非整数,将
asInt()替换为asText(),路径列表改为List<String>即可。 - 子节点字段名适配:如果子节点字段不是
children,修改代码中currentNode.get("children")的字段名(比如subNodes)。 - 路径格式转换:若需要将路径转为逗号分隔的字符串(如
"1,3,6"),可以用以下方式转换:List<String> stringPaths = paths.stream() .map(path -> String.join(",", path.stream().map(String::valueOf).toArray(String[]::new))) .toList();
内容的提问来源于stack exchange,提问作者Emile Dadou
相关产品推荐
相关产品推荐

