patrykos91
patrykos91

Reputation: 3686

MapStruct - custom mapping of target field based on 2 or more different source objects

I am trying to figure out how to implement the following mapping:

class SuperComplexClass {
    Long value;
    String description;
}

class MapIntoMe {
    
    // Many other fields that is also mapped
    
    SuperComplexClass superComplexObject;
}

class MapFromMe {
    ComplexClassPart1 complexClassPart;
}

class AdditionalData {
    ComplexClassPart2 complexClassPart;
}


@Mapper
public interface SomeFancyMapper {

    @Mapping(target = "superComplexObject", source = "{mfm.complexPart, ad.complexPart}",
             qualifiedByName = "mapSuperComplexObject")
    MapIntoMe mapFromMeIntoMe(MapFromMe mfm, AdditionalData ad);
    

    @Named("mapSuperComplexObject")
    default SuperComplexClass mapSuperComplexObject(ComplexPart1 p1, ComplexPart2 p2) {
        SuperComplexClass superObject = new SuperComplexClass();
        //some logic that calculates and fills superObject]
        return superObject;
    }
}

And now obviously expression like source = "{mfm.complexPart, ad.complexPart}" is not working, but it shows clearly what I would like to achieve.

So far I wasn't able to find the answer if that's possible with this approach and without some ugly workarounds.

Any ideas?

Upvotes: 2

Views: 8292

Answers (1)

Filip
Filip

Reputation: 21471

Currently it is not supported to reuse mapping methods with more than one parameter. That is why something like the expression you shared doesn't work.

However, you could use expression, @AfterMapping or @Context (in case you don't need to use AdditionalData for other mapping) to achieve what you need.

Using Expression

@Mapper
public interface SomeFancyMapper {

    @Mapping(target = "superComplexObject", expression = "java(mapSuperComplexObject(mfm.getComplexPart(), ad.getComplexPart()))")
    MapIntoMe mapFromMeIntoMe(MapFromMe mfm, AdditionalData ad);
    

    default SuperComplexClass mapSuperComplexObject(ComplexPart1 p1, ComplexPart2 p2) {
        SuperComplexClass superObject = new SuperComplexClass();
        //some logic that calculates and fills superObject]
        return superObject;
    }
}

Using @AfterMapping

@Mapper
public interface SomeFancyMapper {

    @Mapping(target = "superComplexObject", ignore = true)
    MapIntoMe mapFromMeIntoMe(MapFromMe mfm, AdditionalData ad);
    
    @AfterMapping
    default void mapSuperComplexObject(@MappingTarget MapIntoMe target, MapFromMe mfm, AdditionalData ad) {

        SuperComplexClass superObject = new SuperComplexClass();
        //some logic that calculates and fills superObject]
        return superObject;
    }
}

Using @Context

@Mapper
public interface SomeFancyMapper {

    @Mapping(target = "superComplexObject", source = "complexPart",
             qualifiedByName = "mapSuperComplexObject")
    MapIntoMe mapFromMeIntoMe(MapFromMe mfm, @Context AdditionalData ad);
    

    @Named("mapSuperComplexObject")
    default SuperComplexClass mapSuperComplexObject(ComplexPart1 p1, @Context AdditionalData ad) {
        SuperComplexClass superObject = new SuperComplexClass();
        //some logic that calculates and fills superObject]
        return superObject;
    }
}

Keep in mind that when using @Context the parameter annotated with that annotation cannot be used in Mapping#target. It is an additional context that can be passed to other mapping methods or lifecycle methods.

Upvotes: 5

Related Questions