MapStruct: How to map all attributes to first element of a list?

Viewed 14149

I need a mapping to achieve this:

@Mapping(source = "a", target = "result.transactions[0].a"),
@Mapping(source = "b", target = "result.transactions[0].b"),
@Mapping(source = "c", target = "result.transactions[0].c"),
...
Response dataToResponse(DataModel model);

But this syntax does not work (btw: This works with Spring Bean wrapper). A solution like this is just a half-cooked solution:

@AsList
public <T> List<T> asList( T in ) {
    List<T> result = new ArrayList<T>();
    if ( in!=null ) {
       result.add(in);
   }
   return result;
}

This only works for exactly one attribute since it always creates a new list for each attribute. I don't need to map each attribute to the first element of a new list. The list must be reused but I don't know how this works. What is the proper way to achieve that? I thought about something like this:

@Mapping(source = "a", target = "transaction.a"),
@Mapping(source = "b", target = "transaction.b"),
@Mapping(source = "c", target = "transaction.c"),
...
Transaction dataToTransaction(DataModel model);

and then...

@Mapping([use Transaction from b4], target = "result");

But how can I pass the already mapped fields from above? (I'm using the latest final release 1.1.0.Final)

3 Answers

Obviously there is no clean solution for that. So I had to workaround it by exclude the following mapping into a separate mapper:

@Mapping(source = "a", target = "transaction.a"),
@Mapping(source = "b", target = "transaction.b"),
@Mapping(source = "c", target = "transaction.c"),
Transaction dataToTransaction(DataModel model);

In the main mapper, I execute the separate mapper and convert it into a list by expression:

@Mapping(expression = "java(Arrays.asList(SubMapper.INSTANCE.dataToTransaction(model)))", target = "result.transactions")

I think using expression is the workarround here:

public interface MyMapper {
    @Mapping(target = "subjectName", expression = "java(source.getCourses().get(0).getCourseName())")
    Target map(Source source);
} 

also check here for more: https://github.com/mapstruct/mapstruct/issues/1321

in my code i did it like this:

@Mapping(expression = "java(player.getAddressBooks().get(0).getPostCode())", target = "postCode")
@Mapping(expression = "java(player.getAddressBooks().get(0).getCity())", target = "city")
@Mapping(expression = "java(player.getAddressBooks().get(0).getStreetNumber())", target = "streetNumber")
@Mapping(expression = "java(player.getAddressBooks().get(0).getStreetName())", target = "streetName")
@Mapping(expression = "java(player.getAddressBooks().get(0).getCountryISO())", target = "countryISO")

Just came across this question. A bit more elegant solution would be something like this:

@Mapping(source = "a", target = "transaction.a"),
@Mapping(source = "b", target = "transaction.b"),
@Mapping(source = "c", target = "transaction.c"),
Transaction dataToTransaction(DataModel model);
  • this is the same as in the previous answer

What remains to do is to map single transaction to a one-element-list:

default List<Transaction> mapTransactionToList(Transaction source) {
     return ImmutableList.of(source);
}

Now you can simply map model to List<Transaction> in your @Mapping definition and MapStruct should figure out what to do.

I think this looks better and is less error-prone than "expression" based solution.

Please note that you can include the code in your Mapper interface.

Related