问题描述
这个问题似乎很复杂,所以我在这里发布此问题以寻求解决此问题的任何可能方式。
我有地图列表。我再次想要一张地图列表,但要确保将地图转换为某种层次结构。
原始数据:(列表
[
{
"studentId": 101,"name": "John","subjectId": 2001,"marks": 85,"street": "Bakers Street","state": "LA"
},{
"studentId": 101,"subjectId": 2002,"marks": 75,{
"studentId": 102,"name": "Shae","subjectId": 3001,"marks": 96,"street": "Howards","state": "NYC"
}
]
此地图列表将转换为以下地图列表:(List
[
{
"studentId": 101,"academics":
[
{
"subjectId": 2001,"marks": 85
},{
"subjectId": 2002,"marks": 75
}
],"address":
{
"street": "Bakers Street","state": "LA"
}
},"academics":
[
{
"subjectId": 3001,"marks": 96
}
],"address":
{
"street": "Howards","state": "NYC"
}
}
]
作为一个幼稚的解决方案,我尝试手动处理它们(确实很无聊),因此寻找使用流或任何其他可能方式进行处理的有效且简洁的方法。
更新 天真的解决方案如下
public List<Map<String,Object>> transformResultSet(List<Map<String,Object>> flatDataList) {
List<Map<String,Object>> hierarchicalDataList = new ArrayList<Map<String,Object>>();
Map<String,List<Map<String,Object>>> studentIdToStudentDataListMap = new LinkedHashMap<>();
for (Map<Integer,Object> flatData : flatDataList) {
if (studentIdToStudentDataListMap.get(flatData.get("student_id")) == null) {
studentIdToStudentDataListMap.put(Integer.valueOf(flatData.get("student_id").toString()),new ArrayList<Map<String,Object>>());
}
studentIdToStudentDataListMap.get(Integer.valueOf(flatData.get("student_id").toString())).add(flatData);
}
for (Map.Entry<Integer,Object>>> studentFlatDataList : studentIdToStudentDataListMap.entrySet()) {
Map<String,Object> studentHierarchicalDataMap = new LinkedHashMap<String,Object>();
Map<String,Object> studentFlatDataMap = studentFlatDataList.getValue().get(0);
studentHierarchicalDataMap.put("studentId",studentFlatDataMap.get("studentId"));
studentHierarchicalDataMap.put("name",studentFlatDataMap.get("name"));
List<Map<String,Object>> academicslist = new ArrayList<Map<String,Object>>();
for (Map<String,Object> studentDetailAcademic : studentFlatDataList.getValue()) {
Map<String,Object> academic = new LinkedHashMap<String,Object>();
academic.put("subjectId",studentDetailAcademic.get("subjectId"));
academic.put("marks",studentDetailAcademic.get("marks"));
academicslist.add(academic);
}
studentHierarchicalDataMap.put("academics",academicslist);
Map<String,Object> address = new LinkedHashMap<String,Object>();
address.put("street",studentFlatDataMap.get("street"));
address.put("state",studentFlatDataMap.get("state"));
studentHierarchicalDataMap.put("address",address);
hierarchicalDataList.add(studentHierarchicalDataMap);
}
return hierarchicalDataList;
}
解决方法
从您的json示例中,您似乎拥有List<Object>
而不是List<Map<String,Object>>
。
因此, 只是为了给您一个想法 创建2个对象,例如StudentDto
和MarkDto
。
假设输入对象是Student
作为成员的StudentDto
和List<MarkDto>
:
Map<String,List<Student>> map = list.stream().collect(groupingBy(Student::studentId));
Map<String,StudentDto> dtoMap = new HashMap<>();
for(Map.Entry<String,List<Student>> entry : map.entrySet()) {
StudentDto stud = new StudentDto();
//assign other studentDto properties
for(Student std : entry.getValue()) {
MarkDto mark = new MarkDto();
mark.setSubjectId(std.getStudentid());
mark.setMark(entry.getMark()));
stud.add(mark);
}
dtoMap.put(String.valueOf(stud.getId()),stud);
}
return dtoMap.stream().collect(Collectors.toList()); // or return the map itself
,
您可以将算法分为几个步骤:
- 将
subjectId
,marks
提取到新的academics
Map
。 - 将
street
,state
提取到新的address
Map
。 - 用
academics
将Map
List
包裹起来。 - 通过
studentId
合并数据。发生冲突时,我们需要合并academics
List
。
步骤1.
和2.
除了键名相同。我们可以将其提取到一个新的类中,以避免方法引用的重复:
class ExtractKeysToMap implements Function<Map<String,Object>,Map<String,Object>> {
private final List<String> keys;
private final String newKey;
ExtractKeysToMap(String newKey,List<String> keys) {
this.newKey = Objects.requireNonNull(newKey);
this.keys = Objects.requireNonNull(keys);
}
@Override
public Map<String,Object> apply(Map<String,Object> map) {
Map<String,Object> academics = new HashMap<>();
keys.forEach(key -> {
Object value = map.remove(key);
if (value != null) academics.put(key,value);
});
map.put(newKey,academics);
return map;
}
}
由于我们已实现第一步和第二步,因此可以在以下示例中使用它:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./src/main/resources/test.json");
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
CollectionType jsonType = mapper.getTypeFactory().constructCollectionType(List.class,Map.class);
List<Map<String,Object>> response = mapper.readValue(jsonFile,jsonType);
final String academicsKey = "academics";
Collection<Map<String,Object>> result = response
.stream()
.map(new ExtractKeysToMap(academicsKey,Arrays.asList("subjectId","marks")))
.map(new ExtractKeysToMap("address",Arrays.asList("street","state")))
.peek(map -> map.computeIfPresent(academicsKey,(k,v) -> new LinkedList<>(Collections.singletonList(v))))
.collect(Collectors.toMap(
map -> map.get("studentId"),map -> map,(map0,map1) -> {
((List<Object>) map0.get(academicsKey)).addAll((List<Object>) map1.get(academicsKey));
return map0;
}))
.values();
mapper.writeValue(System.out,result);
}
}
上面的代码打印:
[ {
"studentId" : 101,"name" : "John","academics" : [ {
"subjectId" : 2001,"marks" : 85
},{
"subjectId" : 2002,"marks" : 75
} ],"address" : {
"street" : "Bakers Street","state" : "LA"
}
},{
"studentId" : 102,"name" : "Shae","academics" : [ {
"subjectId" : 3001,"marks" : 96
} ],"address" : {
"street" : "Howards","state" : "NYC"
}
} ]
,
似乎您需要按学生分组,同时还要更改json结构。
更高级别,您可以执行此操作(我们将在后面看到详细信息):
Map<Integer,Object>> grouped = flatDataList.stream()
.collect(Collectors.toMap(
s -> (Integer) s.get("studentId"),s -> transformToHierarchicalStudent(s),(oldS,newS) -> mergeHierarchicalStudents(oldS,newS)));
因此,这将创建按studentId
分组的具有分层格式的学生地图。我们将委托给两种方法:一种是从固定学生中创建分层学生,另一种是将具有相同studentId
的两个分层学生合并。
transformToHierarchicalStudent
方法如下:
Map<String,Object> transformToHierarchicalStudent(Map<String,Object> flat) {
Map<String,Object> student = new LinkedHashMap<>();
student.put("studentId",flat.get("studentId"));
student.put("name",flat.get("name"));
Map<String,Object> address = new LinkedHashMap<>();
address.put("street",flat.get("street"));
address.put("state",flat.get("state"));
student.put("address",address);
List<Map<String,Object>> academics = new ArrayList<>();
Map<String,Object> subject = new LinkedHashMap<>();
subject.put("subjectId",flat.get("subjectId"));
subject.put("marks",flat.get("marks"));
academics.add(subject);
student.put("academics",academics);
return student;
}
还有mergeHierarchicalStudents
方法:
Map<String,Object> mergeHierarchicalStudents(
Map<String,Object> oldSt,Object> newSt) {
// We only need to merge the subjects
List<Map<String,Object>> oldAcademics =
(List<Map<String,Object>>) oldSt.get("academics");
List<Map<String,Object>> newAcademics =
(List<Map<String,Object>>) newSt.get("academics");
oldAcademcis.addAll(newAcademics);
return oldS;
}
这假定原始平面列表中没有同一学生的重复科目。
最后,如果您需要List
分级学生,只需获取地图值即可:
List<Map<String,Object>> hierarchicalStudents = new ArrayList<>(grouped.values());