Could you help me with Java Streams
?
As you can see from the title I need to merge List<Map<String, Map<String, Genuineness>>>
into Map<String, Map<String, Genuineness>>
.
The list is represented as List<Map<String, Map<String, Genuineness>>>
and looks like:
[ { "USER_1":{ "APP_1":{ "total":1, "totalGenuine":1, "totalDevelopment":1 } }, "USER_2":{ "APP_1":{ "total":1, "totalGenuine":1, "totalDevelopment":1 }, "APP_2":{ "total":2, "totalGenuine":2, "totalDevelopment":2 } } }, { "USER_1":{ "APP_1":{ "total":1, "totalGenuine":1, "totalDevelopment":1 } }, "USER_2":{ "APP_1":{ "total":1, "totalGenuine":1, "totalDevelopment":1 }, "APP_2":{ "total":2, "totalGenuine":2, "totalDevelopment":2 } } } ]
So, as you can see, duplicate keys could be everywhere.
My goal is to combine them into Map<String, Map<String, Genuineness>>
by merging Genuineness
. Merge Genuineness
simply means return a new object with summed up values total
, totalGenuine
, and totalDevelopment
.
Here is my implementation that fails:
final Map<String, Map<String, Genuineness>> map = taskHandles.stream().map(this::mapTaskHandle) .flatMap(m -> m.entrySet().stream()).collect( Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream() .collect( Collectors.toMap(Map.Entry::getKey, g -> new Genuineness(g.getValue().getTotal(), g.getValue().getTotalGenuine(), g.getValue().getTotalDevelopment()), (g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(), g1.getTotalGenuine() + g2.getTotalGenuine(), g1.getTotalDevelopment() + g2.getTotalGenuine() ) ) ) ) );
It fails with message:
java.lang.IllegalStateException: Duplicate key {TEST_33_33_APP_1=live.attach.billing.domain.model.billing.Genuineness@951b6fe}
So, seems that in my implementation I’ve pointed how to combine inner map but didn’t make a merging of values of outer map and I don’t know how to do it.
I greatly appreciate your help. Thank you in advance!
UPDATE: Expected output:
{ "USER_1":{ "APP_1":{ "total":2, "totalGenuine":2, "totalDevelopment":2 } }, "USER_2":{ "APP_1":{ "total":2, "totalGenuine":2, "totalDevelopment":2 }, "APP_2":{ "total":4, "totalGenuine":4, "totalDevelopment":4 } } }
Answer
Honestly, this is a horrible data structure to work with and the maintainers of this code will have a hard time finding out problems that arise.
You should take a step back and consider refactoring the code, any way you can solve the missing part by using the following merge function in the outmost toMap
:
(l, r) -> { r.forEach((k, v) -> l.merge(k, v, (bi, bii) -> new Genuineness(bi.getTotal() + bii.getTotal(), bi.getTotalGenuine() + bii.getTotalGenuine(), bi.getTotalDevelopment() + bii.getTotalGenuine()))); return l; }
Full code:
taskHandles.stream().map(this::mapTaskHandle) .flatMap(m -> m.entrySet().stream()).collect( Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream() .collect( Collectors.toMap(Map.Entry::getKey, g -> new Genuineness(g.getValue().getTotal(), g.getValue().getTotalGenuine(), g.getValue().getTotalDevelopment()), (g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(), g1.getTotalGenuine() + g2.getTotalGenuine(), g1.getTotalDevelopment() + g2.getTotalGenuine() ) ) ),(l, r) -> { r.forEach((k, v) -> l.merge(k, v, (bi, bii) -> new Genuineness(bi.getTotal() + bii.getTotal(), bi.getTotalGenuine() + bii.getTotalGenuine(), bi.getTotalDevelopment() + bii.getTotalGenuine()))); return l; } ) );