Java:将扁平的List<Map<String, Object>>转换为分层次的List<Map<String, Object>>

4
这个问题看起来相当复杂,所以我在这里发布此问题以寻求可能的解决方法。
我有一组地图。 我想再次得到地图列表,但要确保将地图转换为某种层次结构。
原始数据:(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<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;
}

7
请提供“朴素解法”和你已尝试过的方法。我们不了解你想要实现的对象结构。你确定要使用List<Map<String, Object>>作为输出,而不是一个结构化的对象吗? - Nikolas Charalambidis
5
一个解决方案“无聊”并不一定意味着它不好。 - Slaw
你的帖子不太清楚它是一个问题还是关于使用boring实现。你应该展示一下boring是如何工作的。如果有帮助的话,可以在map上使用groupingBy来基于字段studentId进行分组。尽量让它更清晰明了。 - Aman
请查看类似问题:使用Java Stream对复杂对象列表进行分组,您只需要实现不同的合并函数即可。 - Michał Ziober
@NikolasCharalambidis:请查看更新后带有简单实现的问题。 - Jake
1
@Aman:问题已更新,解决方案有点无聊 :D - Jake
3个回答

1
根据您提供的json示例,看起来您有List<Object>而不是List<Map<String,Object>>。因此,只是为了给您一个想法,创建两个对象,假设是StudentDtoMarkDto。 假设输入对象是StudentStudentDto作为成员拥有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

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

1
看起来是一个可扩展的解决方案。 - Jake

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

    // 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<Map<String, Object>> hierarchicalStudents = new ArrayList<>(grouped.values());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接