8

How I can merge List<Map<String,String>> to Map<String,String> using flatMap?

Here's what I've tried:

final Map<String, String> result = response
    .stream()
    .collect(Collectors.toMap(
        s -> (String) s.get("key"),
        s -> (String) s.get("value")));
result
    .entrySet()
    .forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue()));

This does not work.

ErikE
  • 48,881
  • 23
  • 151
  • 196
math
  • 111
  • 1
  • 1
  • 2
  • 2
    Can map keys collide, or are they guaranteed unique? If they can collide, what action do you want—take the first one, concatenate them together, maintain a count, what? – ErikE Oct 26 '17 at 22:26
  • What are you trying to do ? Concatenate all keys together, same for value ? – Paul Lemarchand Oct 26 '17 at 22:44

3 Answers3

16

Assuming that there are no conflicting keys in the maps contained in your list, try following:

Map<String, String> maps = list.stream()
    .flatMap(map -> map.entrySet().stream())
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
VHS
  • 9,534
  • 3
  • 19
  • 43
  • You'll see in my answer: adding a merge function to the `toMap` call avoids the conflicts issue. – sprinter Oct 26 '17 at 23:47
  • 1
    @sprinter, by merge function do you mean the `Set::stream`? It's not needed. If you add duplicate keys in a map the last one overwrites the previous ones. – VHS Oct 26 '17 at 23:55
  • no that's not how `toMap` works. From the doc: "If the mapped keys contain duplicates (according to Object.equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys might have duplicates, use toMap(Function, Function, BinaryOperator) instead." The binary operator I used in my solution is `Map::putAll` which handles the duplicates. – sprinter Nov 25 '20 at 11:26
6

A very simple way would be to just use putAll:

Map<String, String> result = new HashMap<>();
response.forEach(result::putAll);

If you particularly want to do this in a single stream operation then use a reduction:

response.stream().reduce(HashMap<String, String>::new, Map::putAll);

Or if you really want to use flatMap:

response.stream().map(Map::entrySet).flatMap(Set::stream)
    .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, Map::putAll));

Note the merging function in the final alternative.

sprinter
  • 27,148
  • 6
  • 47
  • 78
4

If you're ok with overriding the keys, you can just merge the Maps into a single map with collect, even without flatMaps:

public static void main(String[] args) throws Exception {
    final List<Map<String, String>> cavia = new ArrayList<Map<String, String>>() {{
        add(new HashMap<String, String>() {{
            put("key1", "value1");
            put("key2", "value2");
            put("key3", "value3");
            put("key4", "value4");
        }});
        add(new HashMap<String, String>() {{
            put("key5", "value5");
            put("key6", "value6");
            put("key7", "value7");
            put("key8", "value8");
        }});
        add(new HashMap<String, String>() {{
            put("key1", "value1!");
            put("key5", "value5!");
        }});
    }};

    cavia
            .stream()
            .collect(HashMap::new, HashMap::putAll, HashMap::putAll)
            .entrySet()
            .forEach(System.out::println);
}

Will output:

key1=value1!
key2=value2
key5=value5!
key6=value6
key3=value3
key4=value4
key7=value7
key8=value8
madhead
  • 31,729
  • 16
  • 153
  • 201