Mapstruct – Custom mapping methods

We have already discussed about MapStruct in a CDI context and how we can basically transform entities to a DTO. In some cases we need to transform data in our request or response. Or abstracted to http-operations, in a GET or POST/PUT operation.

I faced the problem of transforming my entity to a DTO with enums. So I´d like to make an example approach of handle enums in such a situation.

Precondition Basics

Before we start, let´s setup our Maven project. For this, we need our Maven dependencies, an entity, our enum and the corresponding DTO.

Maven Dependencies

To work with Mapstruct and Jersey please add the following dependencies

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.2.0.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.2.0.Final</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-server</artifactId>
    <version>1.19.4</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-core</artifactId>
    <version>1.19.4</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-json</artifactId>
    <version>1.19.4</version>
</dependency>

Entity, Enum and the Database

Our User entity:

public class User implements Serializable {

    private String name;
    private Role role;

    //default constructor 
    //full args constructor 
    //setter and getter

}

Our Role enum:

public enum Role {

    CUSTOMER("Customer"),
    STAFF_CHIEF("Staff with authority"),
    STAFF("Regular staff"),
    TRAINEE_SCHOOL("Trainee from school"),
    TRAINEE_REGULAR("Junior Trainee");

    private final String value;

    private Role(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

To get some data we just create a simple Mock database

public class UserDatabase {
    
    public List<User> getAllUser() {
        
        List<User> users = new ArrayList<>();
        
        User u1 = new User("Elton Winsley", Role.STAFF);
        User u2 = new User("Bruce Lee", Role.CUSTOMER);
        User u3 = new User("Markus Sad", Role.STAFF_CHIEF);
        User u4 = new User("Kimi Jones", Role.TRAINEE_REGULAR);
        User u5 = new User("Franklin Bug", Role.TRAINEE_SCHOOL);
        
        users.add(u1);
        users.add(u2);
        users.add(u3);
        users.add(u4);
        users.add(u5);
        
        return users;
    }
}

REST-Resource

Now we just write a simple GET – Method for our Resource, as we did it a few times.

@GET
@Path("allUser")
@Produces(MediaType.APPLICATION_JSON)
public Response getAllUsers() {

    UserDatabase userDatabase = new UserDatabase();
    List<User> allUser = userDatabase.getAllUser();

    GenericEntity<List<User>> genericEntity
            = new GenericEntity<List<User>>(allUser) {
    };

    return Response.ok().entity(genericEntity).build();
}

After you have deployed the application to the weblogic of your choice, your output of the resource should be something like:

We recieve our user with his role, represent with the constant of the enum. That´s not really pretty isn´t it?

Map Entity to DTO (GET)

Please pay attention to our enum. It has a constructor, which sets an input on the field value. This field is scoped as private and has a getter method to recieve it´s value.

We want to display exactly this value in our GET-Method.

Create the DTO class

On default, our DTO has the attribute role, which is of type role. Now we´ll change this type to string.

public class UserDTO implements Serializable {

    private String name;
    private String role;
  
    //default contstructor
    //getter and setter 
    
}

Role attribute to DTO

We define our Mapstruct mapping as follows:

@Mapper(componentModel = "cdi")
public interface UserMapper {

    @Mappings({
        @Mapping(source = "role.value", target = "role")
    })
    UserDTO mapUserToUserDTO(User user);
    List<UserDTO> mapUsersToUserDTOs(List<User> users);
}

Please pay attention to our @Mapping. The source of our attribute now is the value attribute of Role and the target is the role attribute of our DTO.

GET – Method

To use our DTO class the correct way, we need to change the getAllUsers method.

@Stateless
@Path("user")
public class UserResource {

    @Inject
    private UserMapper userMapper;

    @GET
    @Path("allUser")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getAllUsers() {

        UserDatabase userDatabase = new UserDatabase();
        List<User> allUser = userDatabase.getAllUser();

        List<UserDTO> userDTOs = userMapper.mapUsersToUserDTOs(allUser);

        GenericEntity<List<UserDTO>> genericEntity
                = new GenericEntity<List<UserDTO>>(userDTOs) {
        };

        return Response.ok().entity(genericEntity).build();
    }
}

Your output will now be the value you have defined in the constructor of your enum constant.

 

Map DTO to Entity (POST / PUT)

Imagine your user is making some inputs in your frontend (Angular, JSF, JSP, something like that).
The role can be selected by a combobox, and is displaying the role descriptions (the values in the constructor) we have defined in our backend. This combobox could look like this:

Rework the Mapper

We now need to implement a custom behaviour in our mapper, which forces the Mapstruct Framework to handle the input of the POST / PUT Operation in an other way as it would do by default.

@Mapper(componentModel = "cdi")
public interface UserMapper {

    //Mapping from Entity to DTO


    @Mappings({
        @Mapping(source = "userDTO", target = "role", qualifiedByName = "formRole")
    })
    User mapUserDTOtoUser(UserDTO userDTO);

    @Named("formRole")
    default Role formBundesland(UserDTO userDTO) {

        Role roleEnum = null;

        for (Role r : Role.values()) {
            if (r.getValue().equals(userDTO.getRole())) {
                roleEnum = r;
            }
        }
        return roleEnum;
    }
}

Because we get our Data from a combobox, we can be sure, that it equlas one of the Role values.

Because or UserMapper is an interface, we use the default method way.
It is important, that you annotate your method with the Mapstruct Annotation @Named(“<yourMethodName>”). This references the qualifiedByName = “<yourMethodName>”. 

In the method we just do the basics of iterating through our roles and search for the right one.

PUT-Method

The PUT-Method just recieves a JSON, that will be transformed to a UserDTO. After that it will be mapped from the DTO to our entity, which contains the role of type Role again.

@PUT
@Path("updateUser")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response updateUser(UserDTO userDTO) {

    User user = userMapper.mapUserDTOtoUser(userDTO);
    //saving operation to mock or real database
    GenericEntity<User> genericEntity
            = new GenericEntity<User>(user) {
    };
    return Response.ok().entity(genericEntity).build();
}

Thanks for reading and have fun using the custom mapping in Mapstruct. 

Feel free to share

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.