Use another MapStruct mapper only inside an expression clause
Asked Answered
B

2

7

I have a mapper that, for a particular attribute of the target class, needs to choose one from a list of objects inside the source object, and map it using a differente mapper class.

Simplifying it a lot, the Game class contains a list of Transaction objects, and my GameMapper class looks like this:

@Component
@Mapper(injectionStrategy = InjectionStrategy.CONSTRUCTOR, uses = {TransactionMapper.class, EventMapper.class})
public interface GameMapper {

@Mapping(target = "transaction", 
    expression = "java(transactionMapper.transactionToDto(findTransactionForPlayer(game, idPlayer)))")
GameResultDto gameToGameResultDto(Game game, Long idPlayer);

// Some more methods

}

The thing is, EventMapper gets generated as a private final EventMapper eventMapper; attribute inside GameMapperImpl, but TransactionMapper is not, so the build fails because MapStruct cannot find transactionMapper.

My best guess is that this is due to no other methods in GameMapper using TransactionMapper explicitly, so Mapstruct decides that it's not needed and doesn't inject it in the implementation.

So... is there any way to force MapStruct to include the mappers in the uses clause, even it if looks like they are not being used, or any other way to work around this?

Beaufort answered 17/11, 2021 at 13:35 Comment(0)
R
11

My best guess is that this is due to no other methods in GameMapper using TransactionMapper explicitly, so Mapstruct decides that it's not needed and doesn't inject it in the implementation.

That is correct. MapStruct will not inject mappers from Mapper#uses if they are not used by MapStruct.

What you could do is to use an abstract class.

e.g.

@Mapper(injectionStrategy = InjectionStrategy.CONSTRUCTOR, componentModel = "spring", uses = {TransactionMapper.class, EventMapper.class})
public abstract class GameMapper {

    @Autowired
    protected TransactionMapper transactionMapper;

    @Mapping(target = "transaction", 
        expression = "java(transactionMapper.transactionToDto(findTransactionForPlayer(game, idPlayer)))")
    public abstract GameResultDto gameToGameResultDto(Game game, Long idPlayer);

    // Some more methods

}
Rooney answered 20/11, 2021 at 7:59 Comment(3)
has this changed since the answer? I came up with this solution on my end as well, but it'd be a lot cleaner to keep using an interface, and have MapStruct create a constructor that includes my dependency (that is managed by Spring). for now if I only use an expression, such as here, my dependency is not getting added as a constructor parameter.Almuce
I am not sure that I understand your question @agiro. If you MapStruct to automatically detect mappings and not to use expression then you can use Mapper#uses. However, if you do not want to add your class to Mapper#uses then you'll have to do it like in this answerRooney
I tried uses but it only works if I map with source - targetin the @Mapping. I need to use an expression and with that the generator doesn't include my custom class in the generated code.Almuce
B
1

I managed to find a hacky solution. By adding a List<TransactionDto> dummy(List<Transaction> transaction); method to GameMapper, I have tricked MapStruct into thinking I'm really using TransactionMapper for something and it has successfully added it to GameMapperImpl.

I'm still open to non-hacky solutions, though :)

Beaufort answered 17/11, 2021 at 14:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.