【问题标题】:Java: Flat List<Map<String, Object>> to Hierarchical List<Map<String, Object>>Java:平面列表<Map<String, Object>> 到分层列表<Map<String, Object>>
【发布时间】:2020-10-20 08:58:00
【问题描述】:

这个问题似乎很复杂,所以我在这里发布这个问题,以寻求任何可能的方法来解决这个问题。

我有地图列表。我又想要一个地图列表,但要确保将地图转换为某种层次结构。

原始数据:(List>)

[
  {
    "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>)

[
  {
    "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<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;
}

【问题讨论】:

  • 提供“幼稚的解决方案”以及到目前为止您尝试过的方法。我们不知道您想要实现的对象结构。您确定要使用List&lt;Map&lt;String, Object&gt;&gt; 作为输出而不是结构对象吗?
  • 解决方案“无聊”并不一定意味着它很糟糕。
  • 您的帖子不太清楚这是一个问题还是关于使用 boring 实现。你应该展示无聊是如何工作的。如果它有帮助,您可以基于字段studentIdmap 上使用groupingBy。试着让它更清楚一点
  • 看看类似的问题:group list of complex object using java stream,你只需要实现不同的合并功能。
  • @Aman:问题已更新,解决方案有多无聊:D

标签: java jackson gson java-stream linkedhashmap


【解决方案1】:

从您的 json 示例中,您似乎拥有 List&lt;Object&gt; 而不是 List&lt;Map&lt;String, Object&gt;&gt;。 所以,只是为了给你一个想法创建 2 个对象,比如说 StudentDtoMarkDto

假设输入对象是StudentStudentDtoList&lt;MarkDto&gt; 作为成员:

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. 提取subjectIdmarks 到一个新的academics Map
    2. streetstate 提取到新的address Map
    3. List 包裹academics Map
    4. 通过studentId 合并数据。当发生碰撞时,我们需要合并academicsList

    步骤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"
      }
    } ]
    

    【讨论】:

    • 看起来像一个可扩展的解决方案。
    【解决方案3】:

    看来你需要按学生分组,同时还要改变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) {
    
        // 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());
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-22
      • 2020-08-01
      • 1970-01-01
      • 2021-04-29
      • 1970-01-01
      • 1970-01-01
      • 2020-02-14
      • 1970-01-01
      相关资源
      最近更新 更多