Search code examples
javajava-8java-streamremap

Java8 - How to convert a nested maps to list of nested maps gathered by value of inner map's key


I have a nested maps object like below

{12345={{"status":"200","outcome":"Success","message":"Account created"}}
{23121={{"status":"400","outcome":"Exception","message":"Invalid State value"}}
{43563={{"status":"200","outcome":"Success","message":"Account updated"}}
{72493={{"status":"400","outcome":"Exception","message":"Bad Request"}}

I need to transform this into Map<String, List<Map<String, Map<String, String>>> where the key of outer map is value of the status ( 200 or 400 ) and the value is the list of original maps that have status = 200 or 400 or other valid values from the inner map.

So, the new structure would look like

{{200={[{12345={"status":"200","outcome":"Success","message":"Account created"}},
       {43563={"status":"200","outcome":"Success","message":"Account updated"}}
      ]
     },
{400={[{23121={"status":"400","outcome":"Exception","message":"Invalid State value"}},
       {72493={"status":"400","outcome":"Exception","message":"Bad Request"}}
      ]
     }
}

Ultimately, I need to generate a report based on the different stati.

This is what I have started with, but am stuck.

I want to loop through outer map, get the inner map, get the value of status key and add the map to a list based on status code value.

This is how I am doing it using loops

private static Map<String, List<Map<String, Map<String, String>>>> covertToReport(Map<String, Map<String, String>> originalMap) {
    Map<String, List<Map<String, Map<String, String>>>> statusBasedListOfMaps = new TreeMap<>();
    //loop through the map
    //for each key, get the inner map
    //get the status value for each inner map

    List<Map<String, Map<String, String>>> accountsMapsList;
    for (Entry<String, Map<String, String>> entry : originalMap.entrySet()) {
        String accNum = entry.getKey();
        Map<String, String> childMap = entry.getValue();
        String stausVal = childMap.get("status");
        accountsMapsList = statusBasedListOfMaps.get(stausVal) == null ? new ArrayList<>() : statusBasedListOfMaps.get(stausVal);
        accountsMapsList.add((Map<String, Map<String, String>>) entry);
        statusBasedListOfMaps.put(stausVal, accountsMapsList);
    }
    return statusBasedListOfMaps;
}

Of course, the below code doesn't compile, but that is what I am trying to get.

private static void covertToReport(Map<String, Map<String, String>> originalMap) {
    Map<String, List<Map<String, Map<String, String>>>> statusBasedListOfMaps;

    statusBasedListOfMaps = originalMap.entrySet()
       .stream()
       .filter(e -> e.getValue()
          .values()
          .stream()
          .map(innerMap -> Collectors.toList())
       .collect(Collectors.toMap(Map.Entry::getKey, Collectors.toList(e)));

Is this possible?


Solution

  • You can just use Collectors.groupingBy() with Collectors.mapping():

    private static Map<String, List<Map<String, Map<String, String>>>> convertToReport(Map<String, Map<String, String>> originalMap) {
        return originalMap.entrySet().stream()
                .collect(Collectors.groupingBy(e -> e.getValue().get("status"), 
                        Collectors.mapping(Map::ofEntries, Collectors.toList())));
    }
    

    You group by status and then map the associated entry to an own map using Map.ofEntries(). If you are using Java you can use this instead of Map::ofEntries:

    e -> new HashMap<>() {{ put(e.getKey(), e.getValue()); }}
    

    The result will be this:

    200=[
        {12345={status=200, message=Account created, outcome=Success}}, 
        {43563={status=200, message=Account created, outcome=Success}}
    ],
    400=[
        {72493={status=400, message=Invalid State value, outcome=Exception}}, 
        {23121={status=400, message=Invalid State value, outcome=Exception}}
    ]