java mapstruct — mapping a field inside related collection
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 Setphones; > public class Phone
public class EmployeeDto < private Integer id; private String name; private Setphones; > public class PhoneDto
@Mappings(< @Mapping(target = "num", source = "phones.number") >) public abstract List toEmployeeDtoList(List 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?
2 Answers 2
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 -> Set 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).
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); ListtoEmployeeDtoList(List employeeList); >
Also good practice to consider — different mappers for each logic object pair.
@Mapper(uses = ) // this is class level annotation.
MapStruct — Mapping Nested Bean
MapStruct handles nested mapping seemlessly. For example, a Student with Subject as nested bean.
Now create a mapper interface which can map nested objects.
@Mapper public interface StudentMapper
Example
Open project mapping as updated in Mapping Multiple Objects chapter in Eclipse.
Create SubjectEntity.java with following code −
package com.tutorialspoint.entity; public class SubjectEntity < private String name; public String getName() < return name; >public void setName(String name) < this.name = name; >>
Update StudentEntity.java with following code −
package com.tutorialspoint.entity; public class StudentEntity < private int id; private String name; private String classVal; private SubjectEntity subject; public int getId() < return id; >public void setId(int id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public String getClassVal() < return classVal; >public void setClassVal(String classVal) < this.classVal = classVal; >public SubjectEntity getSubject() < return subject; >public void setSubject(SubjectEntity subject) < this.subject = subject; >>
Update Student.java with following code −
package com.tutorialspoint.model; public class Student < private int id; private String name; private String className; private String subject; public int getId() < return id; >public void setId(int id) < this.id = id; >public String getName() < return name; >public void setName(String name) < this.name = name; >public String getClassName() < return className; >public void setClassName(String className) < this.className = className; >public String getSubject() < return subject; >public void setSubject(String subject) < this.subject = subject; >>
Update StudentMapper.java with following code −
package com.tutorialspoint.mapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import com.tutorialspoint.entity.StudentEntity; import com.tutorialspoint.model.Student; @Mapper public interface StudentMapper
Update StudentMapperTest.java with following code −
StudentMapperTest.java
package com.tutorialspoint.mapping; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import org.mapstruct.factory.Mappers; import com.tutorialspoint.entity.StudentEntity; import com.tutorialspoint.entity.SubjectEntity; import com.tutorialspoint.mapper.StudentMapper; import com.tutorialspoint.model.Student; public class StudentMapperTest < private StudentMapper studentMapper = Mappers.getMapper(StudentMapper.class); @Test public void testEntityToModel() < StudentEntity entity = new StudentEntity(); entity.setClassVal("X"); entity.setName("John"); entity.setId(1); SubjectEntity subject = new SubjectEntity(); subject.setName("Computer"); entity.setSubject(subject); Student model = studentMapper.getModelFromEntity(entity); assertEquals(entity.getClassVal(), model.getClassName()); assertEquals(entity.getName(), model.getName()); assertEquals(entity.getId(), model.getId()); assertEquals(entity.getSubject().getName(), model.getSubject()); >@Test public void testModelToEntity() < Student model = new Student(); model.setId(1); model.setName("John"); model.setClassName("X"); model.setSubject("Science"); StudentEntity entity = studentMapper.getEntityFromModel(model); assertEquals(entity.getClassVal(), model.getClassName()); assertEquals(entity.getName(), model.getName()); assertEquals(entity.getId(), model.getId()); assertEquals(entity.getSubject().getName(), model.getSubject()); >>
Run the following command to test the mappings.
Output
Once command is successful. Verify the output.
mvn clean test [INFO] Scanning for projects. . [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mapping --- [INFO] Surefire report directory: \mvn\mapping\target\surefire-reports ------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.tutorialspoint.mapping.DeliveryAddressMapperTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 sec Running com.tutorialspoint.mapping.StudentMapperTest Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec Results : Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 .
How to map extended classes in MapStruct
Gotta question regarding mapStruct. I have case where I extend class from base entity and not sure how to map it. Here is my case. BaseEntity:
public class User extends BaseEntity
public class UserDto extends BaseDto
@Mapper(uses = ) public interface UserMapper
@Mapper public interface BaseMapper
Problem is that I don’t get ID property mapped. Thank you for your time. EDIT: There is no error shown, in mapper implementation (generated code) there is no mapping for that ID:
@Override public User userDtoToUser(UserDto userDto) < if ( userDto == null ) < return null; >UserBuilder user = User.builder(); user.name( userDto.getName() ); user.lastName( userDto.getLastName() ); user.username( userDto.getUsername() ); user.password( userDto.getPassword() ); user.profilePicturePath( userDto.getProfilePicturePath() ); return user.build(); >
Hey GhostCat. I’ve added edit to original question. There is no any error shown, i see that implementation is missing mapping for ID, and i in UserDto object when i run the program, ID is null.
It is Autowired in class and im calling it like this return userMapper.userToUserDto(savedUser); where saved user is User object
Yep, I’m using lombok. And i added necessary configuration in pom, so mapstruct and lombok can play together. One more thing, in that generated code, i see base mapper autowired like this @Autowired private BaseMapper baseMapper;
1 Answer 1
I’m guessing (as you have not put buider code) the problem is that your builder class does not include parent class field. MapStruct makes some assumption while generating code for mapper. From documentation —
The default implementation of the BuilderProvider assumes the following:
- The type has a parameterless public static builder creation method that returns a builder. So for example Person has a public static method that returns PersonBuilder.
- The builder type has a parameterless public method (build method) that returns the type being build In our example PersonBuilder has a method returning Person.
- In case there are multiple build methods, MapStruct will look for a method called build, if such method exists then this would be used, otherwise a compilation error would be created.
If you are using Lombok, you can solve this by using @SuperBuilder as —
@SuperBuilder @Getter @ToString public class UserDto extends BaseDto < private String name; private String lastName; private String username; private String password; private String profilePicturePath; >@Getter @SuperBuilder class BaseDto < private Long id; >@SuperBuilder @Getter @ToString public class User extends BaseEntity < private String name; private String lastName; private String username; private String password; private String profilePicturePath; >@Setter @Getter @SuperBuilder class BaseEntity
And generated could looks like —
@Override public User userDtoToUser(UserDto userDto) < if ( userDto == null ) < return null; >UserBuilder user = User.builder(); user.id( userDto.getId() ); user.name( userDto.getName() ); user.lastName( userDto.getLastName() ); user.username( userDto.getUsername() ); user.password( userDto.getPassword() ); user.profilePicturePath( userDto.getProfilePicturePath() ); return user.build(); >