3

I have an object structure like

class Person{
   String userId;
   String firstName;
   String lastName;
   Set<Courses> courses = new HashSet<Courses>();
}

Database records are like this (actually user firstName and lastName is from another table, but for simplicity I am putting as below):

user1 John  Smith course1 
user1 John  Smith course2
user1 John  Smith course3
user2 Jack  Smith course1
user2 Jack  Smith course2

From database retrieved the results into List<Map<String, Object>> resultSet.

Now I need to group by userId and then map courses into Set and create a List<Person> objects.

Right now I am able to group by userId and collect the courses into set but not able to map firstName and lastName.

Map<Object, Set<Object>> userList = resultSet.stream().collect()
.Collectors.groupingBy( usr -> usr.get("user_id"),
     Collectors.mapping( usr -> usr.get("courses"), Collectors.toSet()) ));

// Result {user1=[course1, course2, course3]}

And then I am creating Person Object

List<Person> = userList.entrySet.stream().
              .map( usr -> new Person(usr.getKey().toString(),
                               (Set<Courses)(Set<?>)usr.getValue()))
              .collect(Collectors.toList())

How can I get the firstName , lastName in the groupingby step and use it to create object ??

2
  • What is the type of resultSet? Commented Apr 8, 2018 at 5:41
  • Type of resultset is List<Map<String,Object>> is the query result from db Commented Apr 8, 2018 at 5:49

3 Answers 3

4

You can use Collectors.toMap instead of Collectors.groupingBy. You will achieve the grouping via the merge function passed to toMap.

It will create some unnecessary Person instances, but the final result will contain what you want.

Map<String,Person> persons = 
    resultSet.stream()
             .collect(Collectors.toMap(usr -> usr.get("user_id"),
                                       usr -> new Person(usr.get("user_id"),usr.get("first_name"),usr.get("last_name"),usr.get("course")),
                                       (p1,p2)->{p1.addCourses(p2.getCourses()); return p1;}));

This is assuming you have the relevant constructor and methods in Person class.

Sign up to request clarification or add additional context in comments.

3 Comments

Oh but is it possible to use groupingby as I am trying to learn it
@RinsenS I'm not sure.
Its performance better a bit (p1, p2) -> { if (p1.getCourses().size() > p2.getCourses().size()) { p1.getCourses().addAll(p2.getCourses()); return p1; }else { p2.getCourses().addAll(p1.getCourses()); return p2; }
3

You can also try defining your custom collector passed as a downstream function to a Collectors.groupingBy(). Consider following example:

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class PersonGroupByExample {

    public static void main(String[] args) {

        final List<Map<String, Object>> input = Arrays.asList(
                new HashMap<String, Object>(){{
                    put("userId", "user1");
                    put("firstName", "John");
                    put("lastName", "Smith");
                    put("courses", "course1");
                }},
                new HashMap<String, Object>(){{
                    put("userId", "user1");
                    put("firstName", "John");
                    put("lastName", "Smith");
                    put("courses", "course2");
                }},
                new HashMap<String, Object>(){{
                    put("userId", "user1");
                    put("firstName", "John");
                    put("lastName", "Smith");
                    put("courses", "course3");
                }},
                new HashMap<String, Object>(){{
                    put("userId", "user2");
                    put("firstName", "Jack");
                    put("lastName", "Smith");
                    put("courses", "course1");
                }},
                new HashMap<String, Object>(){{
                    put("userId", "user2");
                    put("firstName", "Jack");
                    put("lastName", "Smith");
                    put("courses", "course2");
                }}
        );

        final Collection<Person> result = input.stream()
                .parallel()
                .collect(Collectors.groupingBy(it -> it.get("userId"), Collector.of(
                        // Start with an empty Person object
                        Person::new,
                        // Collect a list of map objects grouped by the same userId into a single Person object
                        (person, map) -> {
                            // Override common properties
                            person.setUserId(map.getOrDefault("userId", "").toString());
                            person.setFirstName(map.getOrDefault("firstName", "").toString());
                            person.setLastName(map.getOrDefault("lastName", "").toString());
                            // Add person's course to a courses set
                            person.getCourses().add(new Course(map.getOrDefault("courses", "").toString()));
                        },
                        // Combiner function that join partials results (for parallel execution)
                        (person, person2) -> {
                            person.getCourses().addAll(person2.getCourses());
                            return person;
                        }
                ))).values();

        result.forEach(System.out::println);
    }


    static class Person {
        String userId;
        String firstName;
        String lastName;
        Set<Course> courses = new HashSet<>();

        public Person() {}

        public String getUserId() {
            return userId;
        }

        public void setUserId(String userId) {
            this.userId = userId;
        }

        public String getFirstName() {
            return firstName;
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public void setLastName(String lastName) {
            this.lastName = lastName;
        }

        public Set<Course> getCourses() {
            return courses;
        }

        public void setCourses(Set<Course> courses) {
            this.courses = courses;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "userId='" + userId + '\'' +
                    ", firstName='" + firstName + '\'' +
                    ", lastName='" + lastName + '\'' +
                    ", courses=" + courses +
                    '}';
        }
    }

    static class Course {
        String id;

        public Course(String id) {
            this.id = id;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "Course{" +
                    "id='" + id + '\'' +
                    '}';
        }
    }
}

Collectors.groupingBy() groups all entries by userId field and then a downstream function with custom collector reduces a list of Map<String, Object> entries grouped by the same userId to a single Person instance containing all courses. I've used Person and Course POJO's just to illustrate the flow, also input List<Map<String, Object>> is used as an illustration of the transformation process.

In the end we call Map.values() method to return a Collection<Person> instead of Map<String, Person>.

Running this example creates following output:

Person{userId='user1', firstName='John', lastName='Smith', courses=[Course{id='course1'}, Course{id='course3'}, Course{id='course2'}]}
Person{userId='user2', firstName='Jack', lastName='Smith', courses=[Course{id='course2'}, Course{id='course1'}]}

3 Comments

Thanks a lot for the answer, but I am not having input users List as List<Person>. I have having input as a List<Map<String, Object>> , which makes it had to get the course and set in object, can you please help on this...
@RinsenS I've updated the example to use List<Map<String, Object>> as an input.
Awesome, I also just now completed. Thanks for the solution too
0

You should use reducing.

The idea is map each row with a Person object with single courses set, then group by and reducing.

Map<String, Person> people = userList = resultSet.stream()
    .map(v -> toPerson())
    .collect(groupingBy(Person::getId, reducing(null, (p1, p2) -> {
          p1.getCourses().addAll(p2.getCourses); 
          return p1;
    })));

Hope you get the point.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.