
本文介绍如何将键名含多级点号(如 `"employee1.address.street"`)的扁平化 json 数组,安全、递归地重构为标准嵌套 json 对象,支持任意深度路径,并基于 org.json 库提供可扩展的生产级实现。
在实际系统集成中,我们常遇到非标准 JSON 输入:字段以点分隔的扁平键(如 "employee1.address.street")表示嵌套结构,而原始数据却以多个单键对象数组形式存在。这种格式虽便于序列化传输,但严重违背 JSON 的语义表达规范,给下游解析带来显著复杂度。本文提供一种通用、可扩展、深度无关的 Java 解决方案,无需依赖重量级框架(如 Jackson 的复杂注解或自定义反序列化器),仅用轻量 org.json:json(即 JSON-Java)即可稳健完成转换。
核心思路是:对每个扁平键执行「路径拆解 → 逐层创建/定位父节点 → 终止节点赋值」。关键在于抽象出 setJsonPath() 方法,它接收目标 JSONObject、完整路径字符串(如 "employee1.address.street")和待设值,自动按 . 分割并遍历创建中间 JSON 对象,最终将值注入叶子节点。
以下是完整可运行示例(需引入 Maven 依赖:
import org.json.*;
public class JsonFlattenedToNested {
public static JSONObject arrayToStructure(JSONArray inputArray) {
JSONObject output = new JSONObject();
for (Object o : inputArray) {
if (!(o instanceof JSONObject)) {
throw new IllegalArgumentException("Array element must be JSONObject");
}
JSONObject jso = (JSONObject) o;
if (jso.length() != 1) {
throw new IllegalArgumentException("Each object must contain exactly one key-value pair");
}
String key = jso.keys().next();
Object value = jso.get(key);
setJsonPath(output, key, value);
}
return output;
}
private static void setJsonPath(JSONObject target, String key, Object value) {
String[] parts = key.split("\\.");
if (parts.length == 0) return;
JSONObject current = target;
// 遍历除最后一个部分外的所有层级
for (int i = 0; i < parts.length - 1; i++) {
String part = parts[i];
if (!current.has(part)) {
current.put(part, new JSONObject());
}
Object next = current.get(part);
if (!(next instanceof JSONObject)) {
throw new IllegalArgumentException(
String.format("Path conflict: '%s' is not a JSONObject but %s",
String.join(".", parts, 0, i + 1), next.getClass().getSimpleName()));
}
current = (JSONObject) next;
}
// 设置最终键值(自动覆盖已存在键,业务需明确策略)
current.put(parts[parts.length - 1], value);
}
// 使用示例
public static void main(String[] args) {
String inputJson = """
{
"Employee": [
{"employee1.id": 1},
{"employee1.name": "John"},
{"employee1.address.street": "main street"},
{"employee1.address.pin": "123456"},
{"employee2.id": 2},
{"employee2.name": "Mike"},
{"employee2.address.street": "51 street"},
{"employee2.address.pin": "234543"},
{"employee1.address.tempAddress.city": "Beijing"},
{"employee1.address.permanentAddress.city": "Shanghai"}
]
}""";
JSONObject root = new JSONObject(inputJson);
JSONArray employeeArray = root.getJSONArray("Employee");
JSONObject result = arrayToStructure(employeeArray);
System.out.println(result.toString(2)); // 格式化输出,缩进为2
}
}✅ 关键特性与注意事项:
立即学习“Java免费学习笔记(深入)”;
- 任意深度支持:setJsonPath() 通过循环处理 parts.length - 1 层中间节点,天然支持 a.b.c.d.e 等超深路径;
- 健壮性增强:相比原始示例,本实现添加了输入校验(单键约束、类型检查),并在路径冲突时抛出明确异常,避免静默覆盖导致的数据丢失;
- 业务策略显式化:当前逻辑对重复键(如两次 "employee1.id")采用后写覆盖。若需严格拒绝重复,可在 current.put(...) 前增加 if (current.has(...)) throw ...;
- 扩展建议:如需支持数组路径(如 "user.orders[0].item"),可进一步正则解析 \[d+\] 模式并插入 JSONArray;对于大规模数据,可考虑使用 com.fasterxml.jackson.databind.JsonNode 配合 JsonPointer 提升性能与类型安全。
该方案平衡了简洁性、可维护性与工程鲁棒性,是处理“伪嵌套”JSON 的经典范式,适用于 ETL 清洗、API 协议适配及遗留系统对接等典型场景。










