Collectors with nested group by for maps (java 8)

270 Views Asked by At

I am trying to process a List which has the following format: List<Map<String, Map<String, Item>> to Map<String, Map<String, List<Item>> where Item is an object which contains two attributes.

So for the input:

[
 <"A1", <"B1", Item_1>>,
 <"A1", <"B1", Item_2>>,
 <"A1", <"B2", Item_3>>,
 <"A2", <"B1", Item_4>>,
 <"A2", <"B2", Item_5>>,
 <"A2", <"B2", Item_6>>
]

The output should be:

"A1" {
    <"B1", [Item_1, Item_2]>
    <"B2", [Item_3]>
}
"A2" {
    <"B1", [Item_4]>
    <"B2", [Item_5, Item_6]>
}

I tried by using:

list.stream()
.flatMap(it -> it.entrySet().stream())
.collect(groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValues, Collectors.toList())));

but the result is not the desired one, it returns Map<String, List<Map<String, Item>>>

{
"A1": [
   <"B1", Item_1>,
   <"B1", Item_2>,
   <"B2", Item_3>
  ]
"A2": [
   <"B1", Item_4>,
   <"B2", Item_5>,
   <"B2", Item_6>
  ]
}

Can you please advice how can I proceed by grouping also by B_ keys? Thank you!

1

There are 1 best solutions below

0
On

You can proceed by grouping with B_ keys if you could have flattened the entries to a triplet data structure. While this is feasible with map entries, it's rather confusing in terms of implementations. A better choice is to create a data structure of your own to temporarily hold those three flattened attributes.

Here is one on the similar lines that I would be using further in the solution.

@Getter
@AllArgsConstructor
static class Holder {
    String a;
    String b;
    Item c;
}

Now the other important point is to ensure you flatten the structure until the innermost entries and not just the outer Map as shared in your code. This would require nested flatMap to be brought into use.

list.stream()
        .flatMap(out -> out.entrySet().stream()
                .flatMap(e -> e.getValue().entrySet().stream()
                        .map(in -> new Holder(e.getKey(), in.getKey(), in.getValue()))))

By the end of it, what you would have is a Stream<Holder> which can further be grouped twice and mapped to infer the expected result. Here is the complete solution towards that:

list.stream()
        .flatMap(out -> out.entrySet().stream()
                .flatMap(e -> e.getValue().entrySet().stream()
                        .map(in -> new Holder(e.getKey(), in.getKey(), in.getValue()))))
        .collect(Collectors.groupingBy(Holder::getA,
                Collectors.groupingBy(Holder::getB,
                        Collectors.mapping(Holder::getC,
                                Collectors.toList()))));