12

I am currently working with a List<Map<String, Object>> where I am trying to group across various keys within the map. This seems to work nicely using the Java 8 Streams:

Map<Object, Map<Object, List<Map<String, Object>>>> collect =
   list
   .stream()
   .collect(Collectors.groupingBy(
       item -> item.get("key1"),
       Collectors.groupingBy(item -> item.get("key2"))
   ));

As expected this give me a Map<Object, Map<Object, List<Map<String, Object>>>> which works well where the possible grouped results are greater than 1.

I have various examples where the grouping being done will always result in a single item in the lowest level list, for example.

List of Rows

{
  [reference="PersonX", firstname="Person", dob="test", lastname="x"],
  [reference="JohnBartlett", firstname="John", dob="test", lastname="Bartlett"]
}

Grouped by reference

Currently - grouped into a list with 1 Map<String,Object>

[PersonX, { [reference="PersonX", firstname="Person", dob="test", lastname="x"]}],
[JohnBartlett, { [reference="JohnBartlett", firstname="John", dob="test", lastname="Bartlett"]}]

Preference - No List just a single Map<String,Object>

[PersonX, [reference="PersonX", firstname="Person", dob="test", lastname="x"]],
[JohnBartlett, [reference="JohnBartlett", firstname="John", dob="test", lastname="Bartlett"]]

is there a way within the streams to force the output for these instances to be Map<Object, Map<Object, Map<String, Object>>> - so a single Map<String,Object> rather than a List of them?

Any help would be greatly appreciated.

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
John Bartlett
  • 370
  • 1
  • 3
  • 13
  • 1
    Does this answer your question? [Java 8 List into Map](https://stackoverflow.com/questions/20363719/java-8-listv-into-mapk-v) – M. Justin May 27 '20 at 23:06

2 Answers2

18

If I understood correctly, then for the cases where you are sure that there is a single item, you should just replace:

 .collect(Collectors.groupingBy(
   item -> item.get("key1"),
   Collectors.toMap(item -> item.get("key2"), Function.identity())
 ));

You can even provide a third argument as a BinaryOperator to merge your same entries (in case you need to)

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • What happens if there in fact are multiple values with the same key? I guess the value gets overwritten by later occurrences? Your answer would be even stronger with an explanation of this. It might be a decisive factor when deciding if to use this way of collecting to a map. – Magnilex Jun 21 '18 at 06:13
  • @Magnilex *What happens if there in fact are multiple values with the same key*, well care to provide a simple example? Generally, `toMap` works per Key basis, so as soon as you reach an "second time" same key, without a merge function, `toMap` will fail with an Exception. If you meant something else, please clarify – Eugene Jun 21 '18 at 07:09
  • If I understand you correctly, that is what I meant. Take an example where you have a list of entities, collecting to a map with their id:s as key. Would the above method throw an exception if there where multiple entities with the same id? – Magnilex Jun 21 '18 at 07:25
  • 1
    @Magnilex yes, that would throw an Exception with some duplicate error message, unless you provide a merge Function as the third argument of `toMap` – Eugene Jun 21 '18 at 08:40
4

Collectors.toMap() does exactly what you're looking to do.

Map<Object, Map<String, Object>> collect = maps.stream()
        .collect(Collectors.toMap(p -> p.get("reference"), Function.identity()));

Output:

{
  PersonX={firstname=Person, reference=PersonX, lastname=x, dob=test},
  JohnBartlett={firstname=John, reference=JohnBartlett, lastname=Bartlett, dob=test}
}

This will throw an IllegalStateException if you have a duplicate key, which is probably exactly what you want when you never expect there to be a duplicate record in your data:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key JohnBartlett (attempted merging values {dob=test, lastname=Bartlett, reference=JohnBartlett, firstname=John} and {dob=test 2, lastname=Bartlett, reference=JohnBartlett, firstname=John})
    at java.base/java.util.stream.Collectors.duplicateKeyException(Collectors.java:133)
    at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
    at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at so.UniqueKeyStreamExample.main(UniqueKeyStreamExample.java:22)
M. Justin
  • 14,487
  • 7
  • 91
  • 130