
本文介绍如何将扁平化点号分隔键(如 `"employee1.address.street"`)的 json 数据,动态转换为标准嵌套 json 结构,支持任意深度路径,并基于 `org.json` 库提供健壮、可扩展的实现。
在实际开发中,我们常需处理非标准 JSON 输入——例如键名采用点号(.)编码嵌套路径的扁平结构。这类格式虽便于序列化传输,但直接使用极不直观,也不符合 JSON 的语义规范。本文提供的解决方案可自动解析任意深度的点号路径(如 employee1.address.tempAddress.city),递归构建嵌套 JSONObject,最终输出结构清晰、符合 REST/Schema 规范的标准 JSON。
核心实现逻辑
核心思想是:将每个 "key.path.to.field": value 拆解为路径数组 ["key", "path", "to", "field"],逐级导航目标对象,缺失层级则自动创建 JSONObject,最终在叶子节点设置值。
以下为完整可运行代码(依赖 JSON-Java:org.json:json:20240303):
import org.json.*;
public class JsonPathFlattener {
/**
* 将扁平 JSON 数组(每个元素含单个点号路径键值对)转换为嵌套结构
*/
public static JSONObject flattenToNested(JSONArray inputArray) {
JSONObject result = new JSONObject();
for (int i = 0; i < inputArray.length(); i++) {
Object item = inputArray.get(i);
if (!(item instanceof JSONObject)) {
throw new IllegalArgumentException("Array item must be JSONObject, got: " + item.getClass());
}
JSONObject entry = (JSONObject) item;
if (entry.length() != 1) {
throw new IllegalArgumentException("Each object must contain exactly one key-value pair");
}
String key = entry.keys().next();
Object value = entry.get(key);
setJsonPath(result, key, value);
}
return result;
}
/**
* 根据点号路径(如 "a.b.c")向 target 写入 value,自动创建中间对象
*/
private static void setJsonPath(JSONObject target, String path, Object value) {
String[] parts = path.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 IllegalStateException(
String.format("Path conflict at '%s': expected JSONObject but found %s",
String.join(".", parts, 0, i + 1), next.getClass().getSimpleName()));
}
current = (JSONObject) next;
}
// 设置最终字段(若已存在,将被覆盖;可根据业务需求改为抛异常或合并)
String lastKey = parts[parts.length - 1];
current.put(lastKey, value);
}
// 使用示例
public static void main(String[] args) {
String inputJson = """
[
{"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"}
]
""";
JSONArray inputArray = new JSONArray(inputJson);
JSONObject output = flattenToNested(inputArray);
System.out.println(output.toString(2)); // 格式化缩进输出
}
}关键特性与注意事项
- ✅ 任意深度支持:split("\\.") + 循环导航天然支持 a.b.c.d.e... 等无限嵌套层级;
- ✅ 自动结构创建:中间路径不存在时自动新建 JSONObject,无需预定义 schema;
- ⚠️ 冲突处理策略明确:当前实现对重复键(如两次 "employee1.id")采用后写覆盖;生产环境建议增加校验(如首次写入后抛 IllegalStateException);
- ⚠️ 类型安全提醒:确保路径中非末尾节点均为 JSONObject,否则抛出清晰异常,避免静默失败;
- ? 安全性提示:该方案不验证键名合法性(如是否含恶意字符),若输入来自不可信源,应在 setJsonPath 前对 parts 进行白名单校验(如 part.matches("[a-zA-Z_][a-zA-Z0-9_]*"));
- ? 轻量依赖:仅需 org.json:json,无反射或注解,兼容 Android 与 Java SE。
总结
此方案并非“银弹”,而是针对特定数据契约(扁平点号键 → 嵌套结构)的精准工具。它规避了复杂框架(如 Jackson 的 @JsonUnwrapped 或自定义 Deserializer)的学习成本与配置开销,在 ETL、API 适配、遗留系统集成等场景中极具实用价值。只要输入格式稳定、语义明确,该实现即可作为可靠的基础组件嵌入你的 JSON 处理流水线。
立即学习“Java免费学习笔记(深入)”;










