Reputation: 8033
I want to use mapstruct library for mapping models list to dto list in my spring application. Suppose I have two models something like this:
public class Employee {
private Integer id;
private String name;
private Set<Phone> phones;
}
public class Phone {
private Integer id;
private String number;
}
And here is my two dtos:
public class EmployeeDto {
private Integer id;
private String name;
private Set<PhoneDto> phones;
}
public class PhoneDto {
private Integer id;
private String num;
}
And finally I'm using this method in my mapping class:
@Mappings({
@Mapping(target = "num", source = "phones.number")
})
public abstract List<EmployeeDto> toEmployeeDtoList(List<Employee> employeeList);
But this returns me java: No property named "phones.number" exists in source parameter(s).
when I want to compile. I know something is wrong with my code, but I can't find something useful for my need. Can you please help me for solving this problem?
Upvotes: 3
Views: 21952
Reputation: 28
PhoneMapper.class:
@Mapper
public interface PhoneMapper {
@Mapping(target = "num", source = "number")
PhoneDto toDto(Phone phone);
}
EmployeeMapper.class
@Mapper(uses = PhoneMapper.class)
public interface EmployeeMapper {
List<EmployeeDto> toDto(List<Employee> e);
}
That should work
Upvotes: 0
Reputation: 743
First reason: you should specify object -> object
mapping before you can specify collection -> collection
mapping(PhoneDto -> Phone, EmployeeDto -> Employee) as mapstruct nesting notation does not extend into collections. And from my perspective you don't need to hold basic collection mappings within the mapper. You always can do:
employees.stream()
.map(mapper::toDto)
.collect(Collectors.toList());
Note: But if you need some specific collection -> collection
mapping on nested collection, you should specify it. (in your case Set might be ordered using LinkedHashSet underneath, and if you don't specify collection -> collection mapping, you would lose ordering, because mapstruct would use HashSet as default implementation for Set<Phones> -> Set<PhonesDto>
transformation).
Mapstruct would pick all the mapping chain if the mapping is accessible for the mapper (the nested class mappers should be in the same class or would be stated in @Mapper(uses=
class annotation).
Second reason: Yours @Mapping(target = "num", source = "phones.number")
<<-- won't work because it doesn't know from what element from phones
collection the number
should be retrieved. It's like you're trying to write EmployeeDto.num(single record) = Emloyee.phones(multiple records).number(single record)
.
IMHO block: Best practice for using mapstruct is using clean interfaces. That shows that you have clear and transparent structure and good relations within your entity/dto/view/model/etc. If there would be need for something more concrete - you can always specify default method with @AfterMapping
or @BeforeMapping
annotation. Or go to abstract class implementation/decorators (@DecoratedWith
mapping).
There is some dirty hack for such cases - @Mapping(target = "num", expression = "java(your_java_code_as_string_in_here)") but be aware: that expression is a string, and will fail only on mappers creation and won't work in all kinds of refactoring.
This is example mapping for your models (in both ways):
@Mapper
public interface EmployeeMapper {
Employee toEmployee(EmployeeDto employeeDto);
EmployeeDto toEmployeeDto(Employee employee);
@Mapping(target="number", source="num")
Phone toPhone(PhoneDto phoneDto);
@InheritInverseConfiguration
PhoneDto toPhoneDto(Phone phone);
List<EmployeeDto> toEmployeeDtoList(List<Employee> employeeList);
}
Also good practice to consider - different mappers for each logic object pair.
@Mapper(uses = {PhoneMapper.class, OtherMapper.class}) // this is class level annotation.
The great examples are gathered here: https://github.com/mapstruct/mapstruct-examples/
Upvotes: 7
Reputation: 87
I found your answer located here https://www.baeldung.com/mapstruct
I believe that your issue is that you dont actually want the value phones.number to be mapped to num. You want the value number from the Phone class to map to the value num from the PhoneDto class.
@Mappings({
@Mapping(target = "num", source = "number")
})
public abstract List<EmployeeDto> toEmployeeDtoList(List<Employee> employeeList);
Upvotes: -2