I am trying to convert List<Object>
to Map<String, List>
using Streams
,
public class User{
String name;
String age;
String org;
}
I have List<Users>
, and need to collect into Map<String, Object> m
,
m.put("names", List of names,);
m.put("age", List of age);
m.put("org", List of org);
to be use in named query -> eg: select * from table ... where names in (:names) and age in (:age) and org in (:org)
as of now I am doing like
List<String> names = userList.stream().map(User::getName).collect(Collectors.toList());
List<String> age= userList.stream().map(User::getAge).collect(Collectors.toList());
List<String> org= userList.stream().map(User::getName).collect(Collectors.toList());
How to collect all the values while streaming to the list only once ?
I believe something like this should work:
Map<String,List<String>> map =
userList.stream()
.flatMap(user -> {
Map<String,String> um = new HashMap<>();
um.put("names",user.getName());
um.put("age",user.getAge());
um.put("org",user.getOrg());
return um.entrySet().stream();
}) // produces a Stream<Map.Entry<String,String>>
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())));
It converts each User
to a Map<String,String>
(containing the 3 required properties indexed by the required keys), and then groups the entries of all the user maps by their keys.
EDIT:
Here's another alternative that creates the Map.Entry
s directly instead of creating the small HashMap
s, so it should be more efficient:
Map<String,List<String>> map =
userList.stream()
.flatMap (user -> Stream.of (new SimpleEntry<>("names",user.getName()),
new SimpleEntry<>("age",user.getAge()),
new SimpleEntry<>("org",user.getOrg())))
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())));
Eran's showed you how you can accomplish this with streams. As you can hopefully see, it's incredibly ugly.
If your issue with your procedural version is the amount of code duplication, there are other ways besides streams that we can use to solve that problem.
I would refactor the collection to its own method:
private static List<String> getProperty(List<User> users, Function<User, String> getter) {
return users.stream().map(getter).collect(Collectors.toList());
}
Map<String,List<String>> map = new HashMap<>();
map.put("names", getProperty(userList, User::getName));
map.put("age", getProperty(userList, User::getAge));
map.put("org", getProperty(userList, User::getOrg));
Both @Eran and @Michael gives a nice solution, I would like to solve your problem with a generic way :
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
List<User> listUsers = ...
//Create a List which hold name of field and its value
List<Map<String, Object>> listMapping = new ArrayList<>();
for (User user : listUsers) {
listMapping.add(fieldNameValue(user));
}
//Here group by the name of the field
Map<String, List<Object>> result = listMapping.stream()
.flatMap(a -> a.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
}
//This method return a Map which hold names of attributes and its values.
static Map<String, Object> fieldNameValue(Object obj) throws IllegalArgumentException, IllegalAccessException {
Map<String, Object> mapping = new HashMap<>();
for (Field field : obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
mapping.put(field.getName(), field.get(obj));
}
return mapping;
}
In this solution you don't care about the number of fields of the type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With