Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we'll explain how to map lists of different element types using the ModelMapper framework. This involves using generic types in Java as a solution to convert different types of data from one list to another.

2. Model Mapper

The main role of ModelMapper is to map objects by determining how one object model is mapped to another called a Data Transformation Object (DTO).

In order to use ModelMapper, we start by adding the dependency to our pom.xml:

<dependency> 
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>2.3.7</version>
</dependency>

2.1. Configuration

ModelMapper provides a variety of configurations to simplify the mapping process. We customize the configuration by enabling or disabling the appropriate properties in the configuration. It's a common practice to set the fieldMatchingEnabled property to true and allow private field matching:

modelMapper.getConfiguration()
  .setFieldMatchingEnabled(true)
  .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE);

By doing so, ModelMapper can compare private fields in the mapping classes (objects). In this configuration, it's not strictly necessary that all fields with the same names exist in both classes. Several Matching Strategies are allowed. By default, a standard matching strategy requires that all source and destination properties must be matched in any order. This is ideal for our scenario.

2.2. Type Token

ModelMapper uses TypeToken to map generic types. To see why this is necessary, let's see what happens when we map an Integer list to a Character list:

List<Integer> integers = new ArrayList<Integer>();
integers.add(1);
integers.add(2);
integers.add(3);

List<Character> characters = new ArrayList<Character>();
modelMapper.map(integers, characters);

Further, if we print out the elements of the characters list we would see an empty list. This is due to the occurrence of type erasure during runtime execution.

If we change our map call to use TypeToken, though, we can create a type literal for List<Character>:

List<Character> characters 
    = modelMapper.map(integers, new TypeToken<List<Character>>() {}.getType());

At compile time, the TokenType anonymous inner case preserves the List<Character> parameter type, and this time our conversion is successful.

3. Using Custom Type Mapping

Lists in Java can be mapped using custom element types.

For example, let's say we want to map a list of User entities to a UserDTO list. To achieve this, we'll call map for each element:

List<UserDTO> dtos = users
  .stream()
  .map(user -> modelMapper.map(user, UserDTO.class))
  .collect(Collectors.toList());

Of course, with some more work, we could make a general-purpose parameterized method:

<S, T> List<T> mapList(List<S> source, Class<T> targetClass) {
    return source
      .stream()
      .map(element -> modelMapper.map(element, targetClass))
      .collect(Collectors.toList());
}

So, then, we could instead do:

List<UserDTO> userDtoList = mapList(users, UserDTO.class);

4. Type Map and Property Mapping

Specific properties such as lists or sets can be added to the User-UserDTO model. TypeMap provides a method for explicitly defining the mapping of these properties. The TypeMap object stores mapping information of specific types (classes):

TypeMap<UserList, UserListDTO> typeMap = modelMapper.createTypeMap(UserList.class, UserListDTO.class);

UserList class contains a collection of Users. Here, we want to map the list of usernames from this collection to the property list of the UserListDTO class. To achieve this, we will create first UsersListConverter class and pass it List <User> and List <String> as parameter types for conversion:

public class UsersListConverter extends AbstractConverter<List<User>, List<String>> {

    @Override
    protected List<String> convert(List<User> users) {

        return users
          .stream()
          .map(User::getUsername)
          .collect(Collectors.toList());
    }
}

From the created TypeMap object we explicitly add Property Mapping by invoking an instance of UsersListConverter class:

 typeMap.addMappings(mapper -> mapper.using(new UsersListConverter())
   .map(UserList::getUsers, UserListDTO::setUsernames));

Inside the addMappings method, an expression mapping allows us to define the source to destination properties with lambda expressions. Finally, it converts the list of users into the resulting list of usernames.

5. Conclusion

In this tutorial, we explained how lists are mapped by manipulating generic types in ModelMapper. We can make use of TypeToken, generic type mapping, and property mapping to create object list types and make complex mappings.  

The complete source code for this article is available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Emmanuel
Emmanuel
2 months ago

Hello, i succesfully implemented this ObjectMapper. Now i have a situation where i have a Product object and a Department object as follows (pseudocode):

class Product
– Description (String)
– Price (BigDecimal)
– Department (Department)

class Department
– Name (String)

My question is: how can i get a ProductDTO like this:

class ProductDTO
– Description (String)
– Price (BigDecimal)
– DepartmentName (String)

I need to bypass all the matching properties by variable name (setFieldMatchingEnabled(true)) and customise the DepartmentName field. I need to map a list of Product objects (List to List)

Thanks in advance.

Loredana Crusoveanu
2 months ago
Reply to  Emmanuel

Hi Emmanuel,
You need to add the explicit mapping for the department’s name. Something like:

modelMapper.typeMap(Product.class, ProductDTO.class).addMappings(mapper -> {
mapper.map(src -> src.getDepartment().getName(),
ProductDTO::setDepartmentName);
});

Note that I haven’t tried running this code, it’s just a quick idea. You can read more about property mappings in the documentation: http://modelmapper.org/user-manual/property-mapping/

Cheers

Emmanuel
Emmanuel
2 months ago

Thank you very much! that was exactly what i was looking for!

Comments are closed on this article!