Java通用三级嵌套JSON扁平化工具开发问题求助
解决JSON扁平化中的数组、数据类型处理与键唯一性问题
嘿,我仔细看了你的需求和现有代码,确实你遇到的几个点都是JSON扁平化工具里的核心问题——数组处理、多数据类型兼容,还有最关键的嵌套键唯一性保障。咱们一步步来解决:
首先先说说你现有代码里的几个明显问题:
- 递归调用
flattenJson(input)是错误的,应该传入当前遍历到的子节点(比如数组里的dataNode或者内层的JsonNode) - 没有维护键的前缀路径,这就导致嵌套的键会重复覆盖,没法区分层级
- 对
JsonNode的类型判断不够细致,直接转Map会丢失原始类型信息,也没法处理像Timestamp这类特殊数值
接下来是完整的解决方案,我会把每个关键点都说明白:
核心思路
要实现三级结构的扁平化(其实可以扩展到任意层级),核心是维护当前节点的键路径前缀,比如处理Rows[0].Keys.device-id时,前缀会从Rows变成Rows[0]再变成Rows[0].Keys,最终拼接成完整的唯一键。同时要针对JsonNode的不同类型做针对性处理。
完整实现代码
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; public class JsonFlattener { public static Map<String, Object> flattenJson(JsonNode input) { Map<String, Object> resultMap = new HashMap<>(); // 从空前缀开始递归处理根节点 flattenNode(input, "", resultMap); return resultMap; } private static void flattenNode(JsonNode node, String currentPrefix, Map<String, Object> resultMap) { if (node.isValueNode()) { // 处理所有值类型:String、Long、Timestamp(这里Timestamp是数值,按Long处理)等 Object value; if (node.isTextual()) { value = node.asText(); } else if (node.isLong()) { value = node.asLong(); } else if (node.isInt()) { value = node.asInt(); } else if (node.isBoolean()) { value = node.asBoolean(); } else if (node.isDouble()) { value = node.asDouble(); } else { // 兜底处理其他类型 value = node.asText(); } // 将当前前缀作为键,值存入结果Map resultMap.put(currentPrefix, value); return; } if (node.isArray()) { ArrayNode arrayNode = (ArrayNode) node; // 遍历数组元素,用[index]作为路径后缀保证唯一性 for (int i = 0; i < arrayNode.size(); i++) { JsonNode element = arrayNode.get(i); String newPrefix = currentPrefix.isEmpty() ? "[" + i + "]" : currentPrefix + "[" + i + "]"; flattenNode(element, newPrefix, resultMap); } return; } if (node.isObject()) { ObjectNode objectNode = (ObjectNode) node; Iterator<Entry<String, JsonNode>> fields = objectNode.fields(); while (fields.hasNext()) { Entry<String, JsonNode> field = fields.next(); String fieldName = field.getKey(); JsonNode childNode = field.getValue(); // 拼接新的前缀:空前缀直接用字段名,非空前缀用"."连接父路径与当前字段 String newPrefix = currentPrefix.isEmpty() ? fieldName : currentPrefix + "." + fieldName; flattenNode(childNode, newPrefix, resultMap); } } } }
关键细节说明
键唯一性保障:
- 处理对象节点时,用
.拼接父前缀和当前字段名(比如Source→Rows[0].Keys.device-id) - 处理数组节点时,用
[索引]作为后缀(比如Rows→Rows[0]),这样数组里的每个元素都有唯一的路径,不会出现键冲突
- 处理对象节点时,用
数据类型处理:
- 通过
JsonNode的isValueNode()判断是否是值类型,然后分别处理isTextual()、isLong()等具体类型,保留原始数据类型,避免转Map时的类型丢失 - 你的示例里
Timestamp是long型数值,这里会直接转成Long类型存储,完全匹配原始数据类型
- 通过
递归逻辑修正:
- 用私有方法
flattenNode来递归,传递当前的键前缀和结果Map,避免每次递归都新建Map,也能正确累积所有键值对 - 不再错误地递归根节点,而是针对当前子节点进行逐层处理
- 用私有方法
使用示例
针对你给出的示例JSON,调用flattenJson(inputNode)后,会得到类似这样的扁平化结果(部分示例):
Source: "source-1" Rows[0].Keys.device-id: "BC04-EBH-N3K-01" Rows[0].Keys.interface-name: "TenGigE0/0/0/39" Rows[0].Timestamp: 1567621527656 Rows[0].inner.donm.id: "0062" Tey.epath: "Cgetail" ...
这样既满足了三级结构的扁平化(其实可以支持任意层级),也解决了数组、数据类型和键唯一性的问题。
内容的提问来源于stack exchange,提问作者wandermonk
相关产品推荐
相关产品推荐

