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:
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.