
本文介绍如何使用 gson 将含重复 `"action"` 键的 json 对象数组(如多个 `"create"` 动作)解析并重组为按动作分组、`"myobject"` 字段合并为列表的新结构,适用于无法修改原始 json 的场景。
在实际集成第三方 API 或处理遗留数据时,常会遇到结构“不规范”的 JSON:同一数组中多个对象拥有相同的业务键(如 "action": "create"),而目标模型却要求将这些对象按该键归并,使重复字段(如 "myObject")变为列表。由于原始 JSON 不可修改,标准反序列化器(如 Gson 或 Jackson)无法直接映射到单个目标对象的 List
解决思路是采用两阶段处理:先以贴近原始 JSON 的结构精确解析,再在内存中按业务逻辑聚合转换,最后序列化为期望格式。以下以 Gson 为例,提供完整、可运行的实现方案。
✅ 定义清晰的领域模型
我们设计三组 POJO 类,职责分离、语义明确:
// 表示原始 JSON 中每个数组元素的结构(注意:key 无引号,实际 JSON 中应为 "myObject" 和 "action")
static class OriginalItem {
@SerializedName("action")
String action;
@SerializedName("myObject")
MyObjectData myObject;
}
// 表示聚合后的目标结构
static class GroupedResult {
@SerializedName("action")
String action;
@SerializedName("myObject")
List<MyObjectData> myObjects;
public GroupedResult(String action, List<MyObjectData> myObjects) {
this.action = action;
this.myObjects = myObjects;
}
}
// 嵌套数据实体
static class MyObjectData {
@SerializedName("name")
String name;
@SerializedName("description")
String description;
}⚠️ 注意:@SerializedName 注解确保字段名与 JSON 键严格匹配(尤其当 Java 字段命名风格不一致时)。若 JSON 使用驼峰或下划线命名,请同步调整注解值。
✅ 解析 → 聚合 → 序列化:三步核心流程
Gson gson = new Gson();
// Step 1: 按原始结构解析为 List<OriginalItem>
String jsonInput = "[\n" +
" {\"myObject\": {\"name\": \"foo\", \"description\": \"bar\"}, \"action\": \"create\"},\n" +
" {\"myObject\": {\"name\": \"baz\", \"description\": \"qux\"}, \"action\": \"create\"}\n" +
"]";
List<OriginalItem> items = gson.fromJson(jsonInput, new TypeToken<List<OriginalItem>>() {}.getType());
// Step 2: 按 action 分组聚合 myObject(保持原始顺序)
Map<String, List<MyObjectData>> grouped = new LinkedHashMap<>();
for (OriginalItem item : items) {
grouped.computeIfAbsent(item.action, k -> new ArrayList<>()).add(item.myObject);
}
// Step 3: 构建目标对象列表
List<GroupedResult> results = grouped.entrySet().stream()
.map(entry -> new GroupedResult(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
// Step 4: 序列化为最终 JSON(可选:格式化输出便于调试)
String outputJson = gson.toJson(results);
System.out.println(gson.toJson(results, new TypeToken<List<GroupedResult>>() {}.getType()));执行后输出符合预期:
[
{
"action": "create",
"myObject": [
{"name": "foo", "description": "bar"},
{"name": "baz", "description": "qux"}
]
}
]✅ 关键注意事项与优化建议
- 顺序保证:使用 LinkedHashMap 而非 HashMap,确保分组结果顺序与输入数组中首次出现 action 的顺序一致;
- 空值安全:生产环境建议在 OriginalItem 中对 action 和 myObject 添加 @Nullable 注解,并在聚合前校验非空,避免 NullPointerException;
- 多 action 支持:当前逻辑天然支持多种 action(如 "create"、"update"、"delete"),自动分组生成多个 GroupedResult;
-
Jackson 兼容方案:若需迁移到 Jackson,仅需替换 Gson 为 ObjectMapper,@SerializedName 替换为 @JsonProperty,TypeToken 替换为 new TypeReference
- >() {},其余聚合逻辑完全复用;
- 性能考量:对于超大数据集(>10k 条),可考虑流式解析(JsonReader)避免全量加载内存,但本例简洁性优先。
该方案不依赖特殊反序列化配置(如 ACCEPT_SINGLE_VALUE_AS_ARRAY),规避了 @JsonAnySetter 在数组上下文中的局限性,以显式、可控、易测试的方式达成目标,是处理“非标准 JSON 归并”问题的稳健实践。










