ps_403
ps_403

Reputation: 23

How to Convert a List Into Map of Map Map<String, Map<String,Object>>

I have a List<Employee> e which I want to convert into Map<String, Map<String,Emp>> where the outer string should be "Name" and the inner string should be "Domain".

       Name Id Domain
e(0) - Emp1, 1, Insurance
e(1) - Emp1, 2, Sales
e(2) - Emp2, 3, Sales
e(3) - Emp4, 4, Marketing

I am tried the following-

e.stream().collect(Collectors.groupingBy(
                                   Employee::getName,
                                   toMap(Employee::getDomain,Emp)));

So the expected output map should look like

<Emp1>
     <Insurance, e(0)>
     <Sales, e(1)>
<Emp2>
     <Sales, e(2)>
<Emp4>
     <Marketing, e(3)>

But I get only unique values, actual output-

<Emp1>
     <Insurance, e(0)>
<Emp2>
     <Sales, e(2)>
<Emp4>
     <Marketing, e(3)>

Can someone tell the best way to do it?

Upvotes: 2

Views: 335

Answers (2)

Naman
Naman

Reputation: 31878

What you are mostly looking for is nested grouping such as :

Map<String, Map<String, List<Employee>>> groupedMap = employees.stream()
        .collect(Collectors.groupingBy(Employee::getName,
                Collectors.groupingBy(Employee::getDomain, Collectors.toList())));

Note - The values are List<Employee> which are the employees grouped by name and then by domain. (Both same clubbed into a List.)


If you were to stricly adhere to getting a single employee corresponding to the specified grouping, the code pretty much works for me with a small modification:

Map<String, Map<String, Employee>> groupedReducedMap = employees.stream()
        .collect(Collectors.groupingBy(Employee::getName,
                Collectors.toMap(Employee::getDomain,
                        Function.identity(), // value as the employee instance
                        (a, b) -> a))); // choose first instance for similar 'domain'

Upvotes: 1

Eran
Eran

Reputation: 393821

Since the output should be Map<String, Map<String,Employee>> and not Map<String, Map<String,List<Employee>>> (i.e. based on your requested output, there cannot be two Employees having the same name and the same domain), you can chain two groupingBy and then use reducing to ensure that each inner group will have a single Employee instead of a List<Employee>:

Map<String, Map<String,Optional<Employee>>> output =
    e.stream()
     .collect(Collectors.groupingBy(Employee::getName,
                                    Collectors.groupingBy(Employee::getDomain,
                                                          Collectors.reducing((x1,x2)->x2))));

The problem with this version of reducing is that it returns an Optional<Employee> instead of Employee, even though we know the Optional will never be empty.

We get can around that with:

  Map<String, Map<String,Employee>> output =
      e.stream()
       .collect(Collectors.groupingBy(Employee::getName,
                                      Collectors.groupingBy(Employee::getDomain,
                                                            Collectors.reducing(e.get(0),
                                                                                (x1,x2)->x2))));

Now, we use a variant of reducing having an identity value, to which we pass an arbitrary Employee instance (it doesn't matter which, since it will always be replaced by the correct instance).

Upvotes: 0

Related Questions