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

基于Java和Jackson动态查找嵌套JSON数组匹配节点的优化问询

更优雅的Jackson动态JSON路径匹配实现方案

针对动态查找嵌套JSON中匹配指定路径规则的需求,这里提供两种基于Jackson特性的优雅实现,替代手动递归:

方案一:JsonPointer + Stream API 遍历树结构

Jackson的JsonPointer原生支持路径定位,结合Java Stream可以把递归逻辑转化为更简洁的流式遍历,同时收集所有匹配节点的完整路径。

实现代码

import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class JsonPathMatcher {
    private final ObjectMapper objectMapper = new ObjectMapper();

    public List<String> findMatchingPaths(JsonNode rootNode, String targetPath) {
        List<String> matchedPaths = new ArrayList<>();
        String[] pathSegments = targetPath.split("/");
        // 跳过路径开头的空串(比如"/x/y"拆分后第一个元素是空)
        int startIndex = pathSegments[0].isEmpty() ? 1 : 0;
        traverseNode(rootNode, "", pathSegments, startIndex, matchedPaths);
        return matchedPaths;
    }

    private void traverseNode(JsonNode currentNode, String currentPath, String[] pathSegments, int segmentIndex, List<String> matchedPaths) {
        if (segmentIndex == pathSegments.length) {
            matchedPaths.add(currentPath);
            return;
        }

        String targetSegment = pathSegments[segmentIndex];
        // 处理对象节点
        if (currentNode.isObject()) {
            currentNode.fields().forEachRemaining(entry -> {
                String fieldName = entry.getKey();
                JsonNode childNode = entry.getValue();
                String newPath = currentPath.isEmpty() ? fieldName : currentPath + "/" + fieldName;
                // 匹配当前层级的路径段,递归进入下一层
                if (fieldName.equals(targetSegment)) {
                    traverseNode(childNode, newPath, pathSegments, segmentIndex + 1, matchedPaths);
                }
                // 若需要支持跨层级的模糊匹配,可保留此递归(遍历所有子节点)
                // traverseNode(childNode, newPath, pathSegments, segmentIndex, matchedPaths);
            });
        }
        // 处理数组节点
        else if (currentNode.isArray()) {
            for (int i = 0; i < currentNode.size(); i++) {
                JsonNode childNode = currentNode.get(i);
                String newPath = currentPath.isEmpty() ? "[" + i + "]" : currentPath + "/[" + i + "]";
                // 数组元素继续遍历,匹配路径规则
                traverseNode(childNode, newPath, pathSegments, segmentIndex, matchedPaths);
            }
        }
    }

    // Stream简化版:先收集所有节点路径,再过滤匹配目标规则的路径
    public List<String> findMatchingPathsWithStream(JsonNode rootNode, String targetPath) {
        return collectAllPaths(rootNode)
                .filter(path -> path.matches(
                        targetPath.replace("/", "\\/")
                                .replace("*", ".*") // 支持通配符*
                ))
                .collect(Collectors.toList());
    }

    // 递归收集所有节点的完整路径
    private List<String> collectAllPaths(JsonNode node) {
        List<String> paths = new ArrayList<>();
        if (node.isObject()) {
            node.fields().forEachRemaining(entry -> {
                String key = entry.getKey();
                JsonNode child = entry.getValue();
                collectAllPaths(child).forEach(childPath -> paths.add(key + "/" + childPath));
                paths.add(key);
            });
        } else if (node.isArray()) {
            for (int i = 0; i < node.size(); i++) {
                JsonNode child = node.get(i);
                collectAllPaths(child).forEach(childPath -> paths.add("[" + i + "]/" + childPath));
                paths.add("[" + i + "]");
            }
        }
        return paths;
    }
}

优势

  • 复用Jackson原生JsonPointer的路径解析能力,避免手动处理路径拆分的冗余代码
  • Stream API简化了路径过滤逻辑,代码更简洁易读
  • 天然支持数组节点的索引路径格式,无需额外适配

方案二:TreeTraversingParser 流处理(适合大JSON)

如果JSON体积较大,不想加载整个树到内存,可以用Jackson的TreeTraversingParser结合流API,边遍历边匹配路径,内存占用更低。

实现代码

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.traverse.TreeTraversingParser;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

public class StreamingJsonPathMatcher {
    private final ObjectMapper objectMapper = new ObjectMapper();

    public List<String> findMatchingPaths(JsonNode rootNode, String targetPath) {
        List<String> matchedPaths = new ArrayList<>();
        Deque<String> currentPathSegments = new LinkedList<>();
        String[] targetSegments = targetPath.split("/");
        int startIndex = targetSegments[0].isEmpty() ? 1 : 0;

        try (JsonParser parser = new TreeTraversingParser(rootNode, objectMapper)) {
            JsonToken token;
            while ((token = parser.nextToken()) != null) {
                switch (token) {
                    case END_OBJECT:
                    case END_ARRAY:
                        if (!currentPathSegments.isEmpty()) {
                            currentPathSegments.pollLast();
                        }
                        break;
                    case FIELD_NAME:
                        String fieldName = parser.getCurrentName();
                        currentPathSegments.addLast(fieldName);
                        checkPathMatch(currentPathSegments, targetSegments, startIndex, matchedPaths);
                        break;
                    case VALUE_STRING:
                    case VALUE_NUMBER_INT:
                    case VALUE_NUMBER_FLOAT:
                    case VALUE_TRUE:
                    case VALUE_FALSE:
                    case VALUE_NULL:
                        checkPathMatch(currentPathSegments, targetSegments, startIndex, matchedPaths);
                        // 处理数组元素的索引路径
                        if (parser.getParsingContext().inArray()) {
                            int index = parser.getParsingContext().getCurrentIndex();
                            currentPathSegments.addLast("[" + index + "]");
                            checkPathMatch(currentPathSegments, targetSegments, startIndex, matchedPaths);
                            currentPathSegments.pollLast();
                        }
                        break;
                    default:
                        // 忽略START_OBJECT/START_ARRAY等无节点名的token
                        break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return matchedPaths;
    }

    // 检查当前路径是否匹配目标规则,支持通配符*
    private void checkPathMatch(Deque<String> currentSegments, String[] targetSegments, int startIndex, List<String> matchedPaths) {
        if (currentSegments.size() != targetSegments.length - startIndex) {
            return;
        }
        boolean match = true;
        int targetIdx = startIndex;
        for (String seg : currentSegments) {
            if (!targetSegments[targetIdx].equals("*") && !seg.equals(targetSegments[targetIdx])) {
                match = false;
                break;
            }
            targetIdx++;
        }
        if (match) {
            String fullPath = "/" + String.join("/", currentSegments);
            if (!matchedPaths.contains(fullPath)) {
                matchedPaths.add(fullPath);
            }
        }
    }
}

优势

  • 流处理模式,无需加载整个JSON树到内存,适合超大JSON文件
  • 边遍历边匹配路径,时间和空间性能更优
  • 原生支持通配符*的路径匹配规则,扩展性强

对比手动递归的优势

  1. 复用Jackson原生API,减少自定义递归的冗余代码,降低出错概率
  2. 代码结构更清晰,路径解析、节点遍历、匹配逻辑职责分离
  3. 扩展性更强,容易添加通配符、数组索引匹配等高级功能
  4. 流处理方案适配大JSON场景,避免内存溢出

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 02:53:10