如何基于指定字段匹配聚合JSON数组并对数值键求和
问题解决:JSON数组按指定字段匹配合并数值求和
核心逻辑
以userid、groupname、segment、exchange四个字段的组合作为唯一分组键,将数组内匹配的对象归为一组,对每组内所有数值类型字段求和,保留非数值字段的一致值(取组内第一个对象的对应值即可),最终输出合并后的JSON结构。
问题排查
你用LinkedHashMap实现时求和正确但结构不符,大概率是两个原因:要么没正确保留固定标识字段的结构,要么处理动态数值字段时没区分类型导致JSON序列化后格式异常。
代码修正示例(Java + Jackson)
1. 定义数据实体类
用来映射JSON对象,同时支持固定字段和动态字段:
import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import java.util.HashMap; import java.util.Map; public class DataRecord { private String userid; private String groupname; private String segment; private String exchange; private Map<String, Object> dynamicFields = new HashMap<>(); // 固定字段的Getter/Setter public String getUserid() { return userid; } public void setUserid(String userid) { this.userid = userid; } public String getGroupname() { return groupname; } public void setGroupname(String groupname) { this.groupname = groupname; } public String getSegment() { return segment; } public void setSegment(String segment) { this.segment = segment; } public String getExchange() { return exchange; } public void setExchange(String exchange) { this.exchange = exchange; } // 动态字段的处理(适配数值和其他类型) @JsonAnyGetter public Map<String, Object> getDynamicFields() { return dynamicFields; } @JsonAnySetter public void setDynamicField(String key, Object value) { dynamicFields.put(key, value); } }
2. 分组求和实现
import com.fasterxml.jackson.databind.ObjectMapper; import java.util.*; import java.util.stream.Collectors; public class JsonMergeHandler { public static void main(String[] args) throws Exception { // 输入JSON示例 String inputJson = "[\n" + " {\n" + " \"userid\": \"user1\",\n" + " \"groupname\": \"groupA\",\n" + " \"segment\": \"seg1\",\n" + " \"exchange\": \"ex1\",\n" + " \"clicks\": 10,\n" + " \"impressions\": 100,\n" + " \"revenue\": 50.5\n" + " },\n" + " {\n" + " \"userid\": \"user1\",\n" + " \"groupname\": \"groupA\",\n" + " \"segment\": \"seg1\",\n" + " \"exchange\": \"ex1\",\n" + " \"clicks\": 5,\n" + " \"impressions\": 50,\n" + " \"revenue\": 25.25\n" + " }\n" + "]"; ObjectMapper mapper = new ObjectMapper(); List<DataRecord> records = mapper.readValue(inputJson, mapper.getTypeFactory().constructCollectionType(List.class, DataRecord.class)); // 按四个标识字段分组 Map<String, List<DataRecord>> grouped = records.stream() .collect(Collectors.groupingBy(record -> String.join("|", record.getUserid(), record.getGroupname(), record.getSegment(), record.getExchange()) )); // 合并每组数据 List<DataRecord> mergedList = new ArrayList<>(); for (List<DataRecord> group : grouped.values()) { DataRecord merged = new DataRecord(); // 复制固定标识字段(取组内第一个对象的值,分组键保证值一致) DataRecord first = group.get(0); merged.setUserid(first.getUserid()); merged.setGroupname(first.getGroupname()); merged.setSegment(first.getSegment()); merged.setExchange(first.getExchange()); // 求和数值字段,保留非数值字段 Map<String, Object> summedFields = new HashMap<>(); for (DataRecord record : group) { record.getDynamicFields().forEach((key, value) -> { if (value instanceof Number) { if (value instanceof Integer) { summedFields.put(key, summedFields.getOrDefault(key, 0) + (Integer) value); } else if (value instanceof Double) { summedFields.put(key, summedFields.getOrDefault(key, 0.0) + (Double) value); } } else { summedFields.putIfAbsent(key, value); } }); } merged.setDynamicField(summedFields); mergedList.add(merged); } // 输出格式化后的JSON String outputJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mergedList); System.out.println(outputJson); } }
3. 关键细节
- 分组键用
|分隔四个字段,避免字段值包含特殊字符导致分组冲突;如果字段值可能包含|,可以改用自定义对象作为分组键。 - 数值类型区分整数和浮点数处理,避免求和后整数被转成浮点数(比如
clicks保持15而非15.0)。 - 非数值字段仅保留组内第一个对象的值,若组内该字段值不一致,需额外添加冲突处理逻辑。
预期输出
上述代码处理输入示例后,会输出符合结构要求的JSON:
[ { "userid" : "user1", "groupname" : "groupA", "segment" : "seg1", "exchange" : "ex1", "clicks" : 15, "impressions" : 150, "revenue" : 75.75 } ]
内容的提问来源于stack exchange,提问作者Abhishek
相关产品推荐
相关产品推荐

