Search code examples
javaintellij-ideajava-8mapstruct

MapStruct QualifiedByName with multiple parameters


I have come across a situation where my mapping method has 3 parameters, and all the three are being used in deriving one of the properties of the target type.

I have created a default mapping method in the interface keeping the logic for deriving the property, now for calling this method, I could use an expression = "java( /*method call here*/ )" in the @Mapping annotation.

Is there any way to do this with any of the mapstruct annotation like @qualifiedByName, I tried commenting the annotation having expression property and used qualifiedByName, but it doesn't work :

@Mapper
public interface OneMapper {

    @Mapping(target="id", source="one.id")
    //@Mapping(target="qualified",expression = "java( checkQualified (one, projId, code) )")
    @Mapping(target="qualified",qualifiedByName="checkQualifiedNamed")
    OneDto createOne (One one, Integer projId, Integer val, String code);

    @Named("checkQualifiedNamed")
    default Boolean checkQualified (One one, Integer projId, Integer val, String code) {
        if(one.getProjectId() == projId && one.getVal() == val && one.getCode().equalsIgnoreCase(code)) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;                   
    }
}

Solution

  • Currently MapStruct does not support mapping methods with multiple source properties.

    However, in your case you can use the @Context from 1.2.0. From what I understand the projId and the code are there just as helper of the mapping, and they are not used to map target properties from.

    So you can do something like (It should work in theory):

    @Mapper
    public interface OneMapper {
    
        @Mapping(target="id", source="one.id")
        @Mapping(target="qualified", qualifiedByName="checkQualifiedNamed")
        OneDto createOne (One one, @Context Integer projId, @Context String code);
    
        @Named("checkQualifiedNamed")
        default Boolean checkQualified (One one, @Context Integer projId, @Context String code) {
            if(one.getProjectId() == projId && one.getCode().equalsIgnoreCase(code)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;                   
        }
    }
    

    Another alternative would be to extract all those properties into a separate class and pass that along (this would allow for multiple parameters of the same type).

    The class would look like:

    public class Filter {
    
        private final Integer projId;
        private final Integer val;
        private final String code;
    
        public Filter (Integer projId, Integer val, String code) {
            this.projId = projId;
            this.val = val;
            this.code = code;
        }
    
        //getters
    }
    

    Your mapper will then look like:

    @Mapper
    public interface OneMapper {
    
        @Mapping(target="id", source="one.id")
        @Mapping(target="qualified", qualifiedByName="checkQualifiedNamed")
        OneDto createOne (One one, @Context Filter filter);
    
        @Named("checkQualifiedNamed")
        default Boolean checkQualified (One one, @Context Filter filter) {
            if(one.getProjectId() == filter.getProjId() && one.getVal() == filter.getVal() && one.getCode().equalsIgnoreCase(filter.getCode())) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;                   
        }
    }
    

    You can then call the mapper like: mapper.createOne(one, new Filter(projId, val, code));