Reputation: 939
I have the following data:
List<Map<String, Object>> products = new ArrayList<>();
Map<String, Object> product1 = new HashMap<>();
product1.put("Id", 1);
product1.put("number", "123");
product1.put("location", "ny");
Map<String, Object> product2 = new HashMap<>();
product2.put("Id", 1);
product2.put("number", "456");
product2.put("location", "ny");
Map<String, Object> product3 = new HashMap<>();
product3.put("Id", 2);
product3.put("number", "789");
product3.put("location", "ny");
products.add(product1);
products.add(product2);
products.add(product3);
I'm trying to stream over the products list, group by the id and for each id have a list on number, while returning a Map that contains three keys: Id, List of number, and a location.
So my output would be:
List<Map<String, Object>>> groupedProducts
map[0]
{id:1, number[123,456], location:ny}
map[1]
{id:2, number[789], location:ny}
I have tried:
Map<String, List<Object>> groupedProducts = products.stream()
.flatMap(m -> m.entrySet().stream())
.collect(groupingBy(Entry::getKey, mapping(Entry::getValue, toList())));
which prints:
{number=[123, 456, 789], location=[ny, ny, ny], Id=[1, 1, 2]}
I realise Map<String, List<Object>>
is incorrect, but it's the best I could achieve to get the stream to work. Any feedback is appreciated.
Upvotes: 2
Views: 1863
Reputation: 42184
In your case grouping by Id
key with Collectors.collectingAndThen(downstream, finisher)
could do the trick. Consider following example:
Collection<Map<String, Object>> finalMaps = products.stream()
.collect(groupingBy(it -> it.get("Id"), Collectors.collectingAndThen(
Collectors.toList(),
maps -> (Map<String, Object>) maps.stream()
.reduce(new HashMap<>(), (result, map) -> {
final List<Object> numbers = (List<Object>) result.getOrDefault("number", new ArrayList<>());
result.put("Id", map.getOrDefault("Id", result.getOrDefault("Id", null)));
result.put("location", map.getOrDefault("location", result.getOrDefault("location", null)));
if (map.containsKey("number")) {
numbers.add(map.get("number"));
}
result.put("number", numbers);
return result;
}))
)
)
.values();
System.out.println(finalMaps);
In the first step you group all maps with the same Id
value to a List<Map<String,Object>>
(this is what Collectors.toList()
passed to .collectingAndThen()
does). After creating that list "finisher" function is called - in this case we transform list of maps into a single map using Stream.reduce()
operation - we start with an empty HashMap<String,Object>
and we iterate over maps, take values from current map in iteration and we set values according to your specification ("Id" and "location" gets overridden, "number" keeps a list of values).
[{number=[123, 456], location=ny, Id=1}, {number=[789], location=ny, Id=2}]
To make code more simple you can extract BiOperator
passed to Stream.reduce
to a method and use method reference instead. This function defines what does it mean to combine two maps into single one, so it is the core logic of the whole reduction.
Upvotes: 2