英文:
Java: Flat List<Map<String, Object>> to Hierarchical List<Map<String, Object>>
问题
这个问题似乎相当复杂,所以我在这里发布这个问题,希望能找到解决的可能方法。
我有一系列的地图。我想再次获得一个地图列表,但要确保将地图转换为某种层次结构。
原始数据:(List<Map<String,Object>>)
[
{
"studentId": 101,
"name": "John",
"subjectId": 2001,
"marks": 85,
"street": "Bakers Street",
"state": "LA"
},
{
"studentId": 101,
"name": "John",
"subjectId": 2002,
"marks": 75,
"street": "Bakers Street",
"state": "LA"
},
{
"studentId": 102,
"name": "Shae",
"subjectId": 3001,
"marks": 96,
"street": "Howards",
"state": "NYC"
}
]
要将这个地图列表转换为以下地图列表:(List<Map<String,Object>>)
[
{
"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"
}
}
]
作为一个天真的解决方案,我试图手动处理它们(真的很无聊),所以寻找使用流或任何其他可能方法进行高效和清晰处理的方法。
更新
天真的解决方案如下:
public List<Map<String, Object>> transformResultSet(List<Map<String, Object>> flatDataList) {
List<Map<String, Object>> hierarchicalDataList = new ArrayList<Map<String, Object>>();
Map<Integer, List<Map<String, Object>>> studentIdToStudentDataListMap = new LinkedHashMap<>();
for (Map<String, 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, List<Map<String, 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;
}
英文:
This issue seems to be pretty complex so here I'm posting this issue for any possible way to solve this.
I have list of Maps. I want again a list of map, but making sure the map is converted into some sort of hierarchy.
Original data: (List<Map<String, Object>>)
[
{
"studentId": 101,
"name": "John",
"subjectId": 2001,
"marks": 85,
"street": "Bakers Street",
"state": "LA"
},
{
"studentId": 101,
"name": "John",
"subjectId": 2002,
"marks": 75,
"street": "Bakers Street",
"state": "LA"
},
{
"studentId": 102,
"name": "Shae",
"subjectId": 3001,
"marks": 96,
"street": "Howards",
"state": "NYC"
}
]
This list of map to be converted to below list of map: (List<Map<String, Object>>)
[
{
"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"
}
}
]
As a naive solution I tried to process them manually (really boring), so looking for any efficient and clean way of doing it using streams or any other possible ways.
UPDATE
The naive solution is as below
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, List<Map<String, 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;
}
答案1
得分: 1
从您的 JSON 示例中看出,您的 List<Object>
实际上是 List<Map<String, Object>>
。因此,仅供参考,创建两个对象,比如 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();
// 分配其他 studentDto 属性
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()); // 或者返回 map 本身
注意:由于您要求只返回翻译后的内容,因此我只提供了代码部分的翻译。
英文:
From your json sample it seems that you have List<Object>
not List<Map<String, Object>>
.
So, just to give you an idea create 2 objects, let's say StudentDto
and MarkDto
.
Assuming that the input object is Student
and StudentDto
having List<MarkDto>
as a member:
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
答案2
得分: 1
你可以将算法分为几个步骤:
- 将
subjectId
、marks
提取到一个新的academics
Map
中。 - 将
street
、state
提取到一个新的address
Map
中。 - 使用一个
List
包装academics
Map
。 - 按照
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"
}
}]
英文:
You can split your algorithm on a few steps:
- Extract
subjectId
,marks
to a newacademics
Map
. - Extract
street
,state
to a newaddress
Map
. - Wrap
academics
Map
with aList
. - Merge data by
studentId
. When collision occurs, we need to mergeacademics
List
.
Steps 1.
and 2.
are the same except key names. We can extract it to a new class to avoid duplication of method references:
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;
}
}
Since we have first and second step implemented we can use it in below example:
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);
}
}
Above code prints:
[ {
"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"
}
} ]
答案3
得分: 1
看起来你需要按学生进行分组,同时还要更改 JSON 结构。
在更高层次上,你可以这样做(稍后我们会看到详细信息):
Map<Integer, Map<String, 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, Map<String, Object> newSt) {
// 我们只需要合并科目
List<Map<String, Object>> oldAcademics =
(List<Map<String, Object>>) oldSt.get("academics");
List<Map<String, Object>> newAcademics =
(List<Map<String, Object>>) newSt.get("academics");
oldAcademics.addAll(newAcademics);
return oldS;
}
这假设原始的平面列表中同一学生没有重复的科目。
最后,如果你需要一个层次化学生的 List
,只需获取映射的值:
List<Map<String, Object>> hierarchicalStudents = new ArrayList<>(grouped.values());
英文:
It seems you need to group by student, while also changing the json structure.
Higher-level, you could do this (we'll see the details later):
Map<Integer, Map<String, Object>> grouped = flatDataList.stream()
.collect(Collectors.toMap(
s -> (Integer) s.get("studentId"),
s -> transformToHierarchicalStudent(s),
(oldS, newS) -> mergeHierarchicalStudents(oldS, newS)));
So, this creates a map of students with hierarchical format, grouped by studentId
. We are delegating to two methods: one that creates a hierarchical student out from a flat student and another one that will merge two hierarchical students that have the same studentId
.
The transformToHierarchicalStudent
method would be as follows:
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;
}
And the mergeHierarchicalStudents
method:
Map<String, Object> mergeHierarchicalStudents(
Map<String, Object> oldSt, Map<String, 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;
}
This assumes there are no duplicate subjects for the same student in the original flat list.
Finally, if you need a List
of hierarchical students, just grab the map values:
List<Map<String, Object>> hierarchicalStudents = new ArrayList<>(grouped.values());
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论