Java: Flat List<Map<String, Object>> to Hierarchical List<Map<String, Object>>

huangapple go评论79阅读模式
英文:

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>>)

[
{
&quot;studentId&quot;: 101,
&quot;name&quot;: &quot;John&quot;,
&quot;subjectId&quot;: 2001,
&quot;marks&quot;: 85,
&quot;street&quot;: &quot;Bakers Street&quot;,
&quot;state&quot;: &quot;LA&quot;
},
{
&quot;studentId&quot;: 101,
&quot;name&quot;: &quot;John&quot;,
&quot;subjectId&quot;: 2002,
&quot;marks&quot;: 75,
&quot;street&quot;: &quot;Bakers Street&quot;,
&quot;state&quot;: &quot;LA&quot;
},
{
&quot;studentId&quot;: 102,
&quot;name&quot;: &quot;Shae&quot;,
&quot;subjectId&quot;: 3001,
&quot;marks&quot;: 96,
&quot;street&quot;: &quot;Howards&quot;,
&quot;state&quot;: &quot;NYC&quot;
}
]

This list of map to be converted to below list of map: (List<Map<String, Object>>)

[
{
&quot;studentId&quot;: 101,
&quot;name&quot;: &quot;John&quot;,
&quot;academics&quot;:
[
{
&quot;subjectId&quot;: 2001,
&quot;marks&quot;: 85
},
{
&quot;subjectId&quot;: 2002,
&quot;marks&quot;: 75
}
],
&quot;address&quot;:
{
&quot;street&quot;: &quot;Bakers Street&quot;,
&quot;state&quot;: &quot;LA&quot;
}
},
{
&quot;studentId&quot;: 102,
&quot;name&quot;: &quot;Shae&quot;,
&quot;academics&quot;:
[
{
&quot;subjectId&quot;: 3001,
&quot;marks&quot;: 96
}
],
&quot;address&quot;:
{
&quot;street&quot;: &quot;Howards&quot;,
&quot;state&quot;: &quot;NYC&quot;
}
}
]

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&lt;Map&lt;String, Object&gt;&gt; transformResultSet(List&lt;Map&lt;String, Object&gt;&gt; flatDataList) {
List&lt;Map&lt;String, Object&gt;&gt; hierarchicalDataList = new ArrayList&lt;Map&lt;String, Object&gt;&gt;();
Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; studentIdToStudentDataListMap = new LinkedHashMap&lt;&gt;();
for (Map&lt;Integer, Object&gt; flatData : flatDataList) {
if (studentIdToStudentDataListMap.get(flatData.get(&quot;student_id&quot;)) == null) {
studentIdToStudentDataListMap.put(Integer.valueOf(flatData.get(&quot;student_id&quot;).toString()), new ArrayList&lt;Map&lt;String, Object&gt;&gt;());
}
studentIdToStudentDataListMap.get(Integer.valueOf(flatData.get(&quot;student_id&quot;).toString())).add(flatData);
}
for (Map.Entry&lt;Integer, List&lt;Map&lt;String, Object&gt;&gt;&gt; studentFlatDataList : studentIdToStudentDataListMap.entrySet()) {
Map&lt;String, Object&gt; studentHierarchicalDataMap = new LinkedHashMap&lt;String, Object&gt;();
Map&lt;String, Object&gt; studentFlatDataMap = studentFlatDataList.getValue().get(0);
studentHierarchicalDataMap.put(&quot;studentId&quot;, studentFlatDataMap.get(&quot;studentId&quot;));
studentHierarchicalDataMap.put(&quot;name&quot;, studentFlatDataMap.get(&quot;name&quot;));
List&lt;Map&lt;String, Object&gt;&gt; academicsList = new ArrayList&lt;Map&lt;String, Object&gt;&gt;();
for (Map&lt;String, Object&gt; studentDetailAcademic : studentFlatDataList.getValue()) {
Map&lt;String, Object&gt; academic = new LinkedHashMap&lt;String, Object&gt;();
academic.put(&quot;subjectId&quot;, studentDetailAcademic.get(&quot;subjectId&quot;));
academic.put(&quot;marks&quot;, studentDetailAcademic.get(&quot;marks&quot;));
academicsList.add(academic);
}
studentHierarchicalDataMap.put(&quot;academics&quot;, academicsList);
Map&lt;String, Object&gt; address = new LinkedHashMap&lt;String, Object&gt;();
address.put(&quot;street&quot;, studentFlatDataMap.get(&quot;street&quot;));
address.put(&quot;state&quot;, studentFlatDataMap.get(&quot;state&quot;));
studentHierarchicalDataMap.put(&quot;address&quot;, address);
hierarchicalDataList.add(studentHierarchicalDataMap);
}
return hierarchicalDataList;
}

答案1

得分: 1

从您的 JSON 示例中看出,您的 List&lt;Object&gt; 实际上是 List&lt;Map&lt;String, Object&gt;&gt;。因此,仅供参考,创建两个对象,比如 StudentDtoMarkDto

假设输入对象是 Student,而 StudentDto 则具有成员 List&lt;MarkDto&gt;

Map&lt;String List&lt;Student&gt;&gt; map = list.stream().collect(groupingBy(Student::studentId)); 
Map&lt;String StudentDto&gt; dtoMap = new HashMap&lt;&gt;();
for(Map.Entry&lt;String List&lt;Student&gt;&gt; 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&lt;Object&gt; not List&lt;Map&lt;String, Object&gt;&gt;.
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&lt;MarkDto&gt; as a member:

Map&lt;String, List&lt;Student&gt;&gt;  map = list.stream().collect(groupingBy(Student::studentId)); 
Map&lt;String, StudentDto&gt;  dtoMap = new HashMap&lt;&gt;();
for(Map.Entry&lt;String, List&lt;Student&gt;&gt; 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

你可以将算法分为几个步骤:

  1. subjectIdmarks提取到一个新的academics Map中。
  2. streetstate提取到一个新的address Map中。
  3. 使用一个List包装academics Map
  4. 按照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:

  1. Extract subjectId, marks to a new academics Map.
  2. Extract street, state to a new address Map.
  3. Wrap academics Map with a List.
  4. Merge data by studentId. When collision occurs, we need to merge academics 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&lt;Map&lt;String, Object&gt;, Map&lt;String, Object&gt;&gt; {
private final List&lt;String&gt; keys;
private final String newKey;
ExtractKeysToMap(String newKey, List&lt;String&gt; keys) {
this.newKey = Objects.requireNonNull(newKey);
this.keys = Objects.requireNonNull(keys);
}
@Override
public Map&lt;String, Object&gt; apply(Map&lt;String, Object&gt; map) {
Map&lt;String, Object&gt; academics = new HashMap&lt;&gt;();
keys.forEach(key -&gt; {
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(&quot;./src/main/resources/test.json&quot;);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
CollectionType jsonType = mapper.getTypeFactory().constructCollectionType(List.class, Map.class);
List&lt;Map&lt;String, Object&gt;&gt; response = mapper.readValue(jsonFile, jsonType);
final String academicsKey = &quot;academics&quot;;
Collection&lt;Map&lt;String, Object&gt;&gt; result = response
.stream()
.map(new ExtractKeysToMap(academicsKey, Arrays.asList(&quot;subjectId&quot;, &quot;marks&quot;)))
.map(new ExtractKeysToMap(&quot;address&quot;, Arrays.asList(&quot;street&quot;, &quot;state&quot;)))
.peek(map -&gt; map.computeIfPresent(academicsKey, (k, v) -&gt; new LinkedList&lt;&gt;(Collections.singletonList(v))))
.collect(Collectors.toMap(
map -&gt; map.get(&quot;studentId&quot;),
map -&gt; map,
(map0, map1) -&gt; {
((List&lt;Object&gt;) map0.get(academicsKey)).addAll((List&lt;Object&gt;) map1.get(academicsKey));
return map0;
}))
.values();
mapper.writeValue(System.out, result);
}
}

Above code prints:

[ {
&quot;studentId&quot; : 101,
&quot;name&quot; : &quot;John&quot;,
&quot;academics&quot; : [ {
&quot;subjectId&quot; : 2001,
&quot;marks&quot; : 85
}, {
&quot;subjectId&quot; : 2002,
&quot;marks&quot; : 75
} ],
&quot;address&quot; : {
&quot;street&quot; : &quot;Bakers Street&quot;,
&quot;state&quot; : &quot;LA&quot;
}
}, {
&quot;studentId&quot; : 102,
&quot;name&quot; : &quot;Shae&quot;,
&quot;academics&quot; : [ {
&quot;subjectId&quot; : 3001,
&quot;marks&quot; : 96
} ],
&quot;address&quot; : {
&quot;street&quot; : &quot;Howards&quot;,
&quot;state&quot; : &quot;NYC&quot;
}
} ]

答案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&lt;Integer, Map&lt;String, Object&gt;&gt; grouped = flatDataList.stream()
.collect(Collectors.toMap(
s -&gt; (Integer) s.get(&quot;studentId&quot;),
s -&gt; transformToHierarchicalStudent(s),
(oldS, newS) -&gt; 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&lt;String, Object&gt; transformToHierarchicalStudent(Map&lt;String, Object&gt; flat) {
Map&lt;String, Object&gt; student = new LinkedHashMap&lt;&gt;();
student.put(&quot;studentId&quot;, flat.get(&quot;studentId&quot;));
student.put(&quot;name&quot;, flat.get(&quot;name&quot;));
Map&lt;String, Object&gt; address = new LinkedHashMap&lt;&gt;();
address.put(&quot;street&quot;, flat.get(&quot;street&quot;));
address.put(&quot;state&quot;, flat.get(&quot;state&quot;));
student.put(&quot;address&quot;, address);
List&lt;Map&lt;String, Object&gt;&gt; academics = new ArrayList&lt;&gt;();
Map&lt;String, Object&gt; subject = new LinkedHashMap&lt;&gt;();
subject.put(&quot;subjectId&quot;, flat.get(&quot;subjectId&quot;));
subject.put(&quot;marks&quot;, flat.get(&quot;marks&quot;));
academics.add(subject);
student.put(&quot;academics&quot;, academics);
return student;
}

And the mergeHierarchicalStudents method:

Map&lt;String, Object&gt; mergeHierarchicalStudents(
Map&lt;String, Object&gt; oldSt, Map&lt;String, Object&gt; newSt) {
// We only need to merge the subjects
List&lt;Map&lt;String, Object&gt;&gt; oldAcademics = 
(List&lt;Map&lt;String, Object&gt;&gt;) oldSt.get(&quot;academics&quot;);
List&lt;Map&lt;String, Object&gt;&gt; newAcademics = 
(List&lt;Map&lt;String, Object&gt;&gt;) newSt.get(&quot;academics&quot;);
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&lt;Map&lt;String, Object&gt;&gt; hierarchicalStudents = new ArrayList&lt;&gt;(grouped.values());

huangapple
  • 本文由 发表于 2020年10月20日 16:58:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/64441790.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定