'Mapstruct: How to use custom mappers with @MappingTarget
I am using Mapstruct and I need to update an existing bean using @MappingTarget, but need to apply some complex logic to set the correct field in the target.
Lets say I have a target bean that looks like this. A user has a list of accounts, and one of those accounts is marked as favourite.
UserDetails {
String name;
List<Account> accounts;
}
Account {
String id;
boolean favourite;
}
The DTO class contains the account ID of their favourite account.
UserDetialsDTO {
String name;
String favouriteAccountId;
List<String> accountIds;
}
I need to use some complex logic to update the correct Account in the list of accounts.
UserDetails fromDto(UserDetialsDTO dto, @MappingTarget UserDetails userDetails);
The logic of finding and updating the correct Account to make it favourite is something like this:
userDetails.accounts
.stream()
.forEach(acct -> acct.setFavourite(dto.favouriteAccountId.equals(acct.id))) ;
How can I tell Mapstruct to use this custom logic when updating a @MapingTarget?
Solution 1:[1]
try:
@Mapper
public interface MyMapper {
@Mapping( target = "accounts", ignore = true )
void fromDto(UserDetialsDTO dto, @MappingTarget UserDetails userDetails);
@AfterMapping
default void handleAccounts(UserDetialsDTO dto, @MappingTarget UserDetails userDetails) {
userDetails.accounts
.stream()
.forEach(acct -> acct.setFavourite(dto.favouriteAccountId.equals(acct.id))) ;
}
}
Solution 2:[2]
You can use a decorator, to implement both:
- initialization of
Accountlist - favorite custom logic
such as:
@Mapper
@DecoratedWith(MyMapperDecorator.class)
public interface MyMapper {
void fromDto(UserDetialsDTO dto, @MappingTarget UserDetails userDetails);
}
public class MyMapperDecorator implements MyMapper{
@Override
public void fromDto(final UserDetialsDTO dto, final UserDetails userDetails) {
if(dto == null){
return;
}
dto.getAccountIds().forEach(a -> {
final Account account = new Account();
account.setId(a);
account.setFavourite(a.equals(dto.getFavouriteAccountId()));
userDetails.getAccounts().add(new Account());
});
}
}
In order to avoid duplicates I would suggest to use Set<Account> instead of List and implement Account.equals() accordingly.
An alternative (sligtly) more sophisticated approach could involve defining a String to Account mapper use it:
@Mapper(uses = AccountMapper.class)
@DecoratedWith(MyMapperDecorator.class)
interface MyMapper {
MyMapper INSTANCE = Mappers.getMapper( MyMapper.class );
@Mapping(target = "accounts", source="accountIds")
abstract void update(UserDetialsDTO dto, @MappingTarget UserDetails userDetails);
}
public abstract class MyMapperDecorator implements MyMapper{
private final MyMapper delegate;
MyMapperDecorator(final MyMapper delegate){
this.delegate = delegate;
}
@Override
public void update(final UserDetialsDTO dto, final UserDetails userDetails) {
if(dto == null){
return;
}
delegate.update(dto, userDetails);
userDetails.accounts
.stream()
.forEach(acct -> acct.setFavourite(dto.favouriteAccountId.equals(acct.id))) ;
}
}
@Mapper
public interface AccountMapper {
Account fromString(String id);
void update(String id, @MappingTarget Account account);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Sjaak |
| Solution 2 |
