基于Josson库递归计算动态JSON表达式值的技术求助
基于Josson的动态JSON表达式递归计算方案
针对你用Josson处理带表达式依赖的动态JSON需求,这里提供一套递归遍历+依赖排序+表达式计算的可行方案,完全适配未知结构的JSON:
核心思路
- 识别表达式节点:递归遍历整个JSON,标记所有包含
expression字段的节点,记录表达式内容和节点路径。 - 解析依赖关系:从表达式中提取引用的字段,构建依赖图谱。
- 拓扑排序:按照依赖顺序排序节点,确保被依赖的字段先计算,避免语法错误。
- 批量计算替换:用Josson的表达式计算能力依次求值,将结果替换原
expression节点。
具体实现(Java示例)
源JSON示例
{ "A": 10, "B": {"expression": "A * 2"}, "C": {"expression": "B + 5"}, "D": { "sub": {"expression": "C / 3"}, "normal": "plain text" }, "list": [ {"value": {"expression": "A + 1"}}, {"value": 20} ] }
处理代码
import com.octomix.josson.Josson; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class JsonExprEvaluator { private static final Pattern DEP_PATTERN = Pattern.compile("([A-Za-z_][A-Za-z0-9_]*)"); private final Josson josson; private final Map<String, Object> jsonData; public JsonExprEvaluator(Map<String, Object> jsonData) { this.jsonData = jsonData; this.josson = Josson.fromJson(jsonData); } // 递归收集所有表达式节点、路径和依赖 private void collectExprs(Object node, String path, Map<String, String> exprMap, Map<String, Set<String>> depMap) { if (node instanceof Map) { Map<String, Object> mapNode = (Map<String, Object>) node; if (mapNode.containsKey("expression")) { String expr = mapNode.get("expression").toString(); exprMap.put(path, expr); // 提取表达式中的依赖字段 Set<String> deps = new HashSet<>(); Matcher matcher = DEP_PATTERN.matcher(expr); while (matcher.find()) { deps.add(matcher.group(1)); } depMap.put(path, deps); } else { for (Map.Entry<String, Object> entry : mapNode.entrySet()) { String newPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey(); collectExprs(entry.getValue(), newPath, exprMap, depMap); } } } else if (node instanceof List) { List<Object> listNode = (List<Object>) node; for (int i = 0; i < listNode.size(); i++) { String newPath = path + "[" + i + "]"; collectExprs(listNode.get(i), newPath, exprMap, depMap); } } } // 拓扑排序处理依赖,确保计算顺序正确 private List<String> topoSort(Map<String, Set<String>> depMap) { Map<String, Integer> inDegree = new HashMap<>(); Queue<String> queue = new LinkedList<>(); List<String> sortedPaths = new ArrayList<>(); // 初始化每个节点的入度 depMap.keySet().forEach(key -> inDegree.put(key, 0)); depMap.values().forEach(deps -> deps.forEach(dep -> inDegree.put(dep, inDegree.getOrDefault(dep, 0) + 1))); // 入度为0的节点优先入队 inDegree.entrySet().stream() .filter(entry -> entry.getValue() == 0) .map(Map.Entry::getKey) .forEach(queue::add); // 依次处理节点 while (!queue.isEmpty()) { String current = queue.poll(); sortedPaths.add(current); depMap.entrySet().stream() .filter(entry -> entry.getValue().contains(current)) .forEach(entry -> { int newDegree = inDegree.get(entry.getKey()) - 1; inDegree.put(entry.getKey(), newDegree); if (newDegree == 0) { queue.add(entry.getKey()); } }); } // 检查循环依赖 if (sortedPaths.size() != depMap.size()) { throw new RuntimeException("JSON中存在循环依赖,无法完成计算"); } return sortedPaths; } // 计算所有表达式并替换原节点 public void evaluateAll() { Map<String, String> exprMap = new HashMap<>(); Map<String, Set<String>> depMap = new HashMap<>(); collectExprs(jsonData, "", exprMap, depMap); List<String> sortedPaths = topoSort(depMap); for (String path : sortedPaths) { String expr = exprMap.get(path); // 用Josson计算表达式 Object result = josson.evaluate(expr); // 根据路径替换原expression节点为计算结果 replaceNodeByPath(jsonData, path, result); } } // 根据路径定位并替换节点 private void replaceNodeByPath(Map<String, Object> root, String path, Object value) { String[] parts = path.split("\\."); Map<String, Object> current = root; for (int i = 0; i < parts.length - 1; i++) { String part = parts[i]; if (part.contains("[")) { // 处理数组元素 String key = part.substring(0, part.indexOf("[")); int idx = Integer.parseInt(part.substring(part.indexOf("[") + 1, part.indexOf("]"))); current = (Map<String, Object>) ((List<Object>) current.get(key)).get(idx); } else { current = (Map<String, Object>) current.get(part); } } String lastPart = parts[parts.length - 1]; if (lastPart.contains("[")) { // 替换数组中的表达式节点 String key = lastPart.substring(0, lastPart.indexOf("[")); int idx = Integer.parseInt(lastPart.substring(lastPart.indexOf("[") + 1, lastPart.indexOf("]"))); List<Object> list = (List<Object>) current.get(key); // 移除expression字段,替换为计算结果(根据你的结构调整,这里假设替换整个value节点) Map<String, Object> item = (Map<String, Object>) list.get(idx); item.put("value", value); item.remove("expression"); } else { // 替换对象中的表达式节点 current.put(lastPart, value); current.remove("expression"); } } public static void main(String[] args) { String jsonStr = "{\n" + " \"A\": 10,\n" + " \"B\": {\"expression\": \"A * 2\"},\n" + " \"C\": {\"expression\": \"B + 5\"},\n" + " \"D\": {\n" + " \"sub\": {\"expression\": \"C / 3\"},\n" + " \"normal\": \"plain text\"\n" + " },\n" + " \"list\": [\n" + " {\"value\": {\"expression\": \"A + 1\"}},\n" + " {\"value\": 20}\n" + " ]\n" + "}"; Map<String, Object> jsonData = Josson.fromJson(jsonStr).getMap(); JsonExprEvaluator evaluator = new JsonExprEvaluator(jsonData); evaluator.evaluateAll(); System.out.println(Josson.fromJson(jsonData).prettyPrint()); } }
关键说明
- 依赖解析:示例用正则提取简单字段名,若你的表达式包含嵌套路径(如
D.sub),可调整正则或用Josson的语法分析工具优化。 - 拓扑排序:彻底解决依赖顺序问题,避免先计算未就绪的字段导致的错误。
- 递归遍历:自动处理嵌套对象和数组,完全适配动态JSON结构。
- Josson优势:用Josson内置的表达式计算替代原生
eval,更安全且原生支持JSON字段引用。
内容的提问来源于stack exchange,提问作者Pranav
相关产品推荐
相关产品推荐

