Search code examples
javaspringmapstruct

use java code instead of mapstruct expression


I am learning to use MapStruct.

I have the following custom mapping configuration that maps a List comes from JPA entity to DTO String that way:

@Mapper(componentModel = "spring")
public interface StatusMapper {

    @Mapping(target = "statusCode",
            expression = "java(entity.getStatuses().get(entity.getStatuses().size() - 1).getStatus())")
    @Mapping(target = "details",
            expression = "java(entity.getStatuses().get(entity.getStatuses().size() - 1).getMessage())")
    StatusResponse map(MyEntity entity);
}

My problem with this solution is that the Java code in the expression actually is a string and the IDE (e.g. IntelliJ) is not checking the syntax of this "java" code. Maybe after a refactor this piece of code will not work anymore because I renamed the related field.

And if I add a null check to the expression then this piece of code will be more longer, and longer code can have more typos.

Can I write here somehow a real java code instead of using this Java expression string?


Solution

  • Keep in mind that MapStruct is an annotation processor, and the expression written in expression will be written one to one in the generated Java class. Therefore, doing a refactoring will lead to a compile error.

    Having said that there is an alternative solution for this.

    @Mapper(componentModel = "spring")
    public interface StatusMapper {
    
        @Mapping(target = "statusCode", source = "statuses", qualifiedByName = "statusesStatus")
        @Mapping(target = "details", source = "statuses", qualifiedByName = "statusesMessage")
        StatusResponse map(MyEntity entity);
    
        @Named("statusesStatus")
        default String extractStatus(Collection<Status> statuses) {
            return statuses != null && statuses.isEmpty() ? statuses.get(statuses.get() - 1).getStatus() : null;
        }
    
        @Named("statusesMessage")
        default String extractStatusMessage(Collection<Status> statuses) {
            return statuses != null && !statuses.isEmpty() ?  statuses.get(statuses.get() - 1).getMessage() : null;
        }
    }
    

    Doing this will make sure that MapStruct uses the custom methods that you have defined.


    Another potentially nice solution (which does not yet exist in MapStruct) and for which there is an open feature request is Allowing indexing for object list.

    The potential solution for that can look like:

    @Mapper(componentModel = "spring")
    public interface StatusMapper {
    
        @Mapping(target = "statusCode", source = "statuses[-1].status")
        @Mapping(target = "details", source = "statuses[-1].message")
        StatusResponse map(MyEntity entity);
    }
    

    If this is something that is interesting for you, I would suggest voting on that issue.