Nested Mapping with Lists

6,691 views
Skip to first unread message

Al Grant

unread,
Sep 20, 2017, 8:41:37 PM9/20/17
to mapstruct-users
Hello,

I am trying to setup a Bean to back a view which has nested mappings and a list.

Person to the top object
Person has a list of Comments
Comments has a property User
User has a Property Organisation

public class PersonViewModel {

public PersonViewModel() {}

private Long id;
private String surname;
private String middlename;
private String firstname;
private String address;
private Date dob;

private List<CommentViewModel> comments = new ArrayList<>();

public class CommentViewModel {

public CommentViewModel(){}

private Long id;
private Date commentDate;
private String comment;
private UserViewModel user;

public class UserViewModel {

public UserViewModel(){}

private Integer id;
private String firstname;
private String surname;
private String email;
private String organisation;

public class OrganisationViewModel {

public OrganisationViewModel(){}

private String name;

So I have DTO's for each of those objects setup.

I also have mapper classes for each object:

@Mapper
public interface PersonMapper {

PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );

@Mappings({
@Mapping(source = "person.address.formattedAddress", target = "address")
})
PersonViewModel PersonToAgencyCommentViewModel(Person person);
}

@Mapper
public interface CommentMapper {
CommentMapper INSTANCE = Mappers.getMapper( CommentMapper.class );
@Mapping(source = "user.organisation.name", target = "user.organisation")
List<CommentViewModel> CommentToAgencyCommentViewModel(List<Comment> comments);
}

@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "organisation.name", target = "organisation")
UserViewModel UserToUserViewModel(User user);
}

@Mapper
public interface OrganisationMapper {

OrganisationMapper INSTANCE = Mappers.getMapper( OrganisationMapper.class );
OrganisationViewModel OrganisationToOrganisationViewModel(Organisations organisation);
}

Now when I try to use build the project there are some build errors:

src\main\java\com\example\mappers\PersonMapper.java:18: error: Can't map property "com.example.domain.Organisations comments[].user.organisation" to "java.lang.String comments[].user.organisation". Consider to declare/implement a mapping method: "java.lang.String map(com.example.domain.Organisations value)".
    PersonViewModel PersonToCommentViewModel(Person person);
                    ^
src\main\java\com\example\mappers\CommentMapper.java:16: error: No property named "user.organisation.name" exists in source parameter(s). Did you mean "empty"?
    @Mapping(source  = "user.organisation.name", target = "user.organisation")
    ^
src\main\java\com\example\mappers\CommentMapper.java:17: error: Can't map property "com.example.domain.Organisations comment.user.organisation" to "java.lang.String commentViewModel.user.organisation". Consider to declare/implement a mapping method: "java.lang.String map(com.example.domain.Organisations value)".
    List<CommentViewModel> CommentToCommentViewModel(List<Comment> comments);
                                 ^
3 errors

What am I doing wrong?

Thanks

Al






Al Grant

unread,
Sep 20, 2017, 11:47:00 PM9/20/17
to mapstruct-users


 I also see that Baeldung has done nested mappings this way:

http://www.baeldung.com/mapstruct (Version Map Struct 1.0)

But the docs for Version 1.2 show the syntax being done differently:


I have also browsed the examples, but that I am mapping a nested object which is a list is also causing me some confusion.

Al Grant

unread,
Sep 21, 2017, 1:09:26 AM9/21/17
to mapstruct-users
Ok, I think I have it working. Hopefully someone can confirm I have done this right. I am documenting it here for the benefit of the community.


My View Models:

public class PersonViewModel {

public PersonViewModel() {}

private Long id;
private String surname;
private String middlename;
private String firstname;
    private String ethnicity;
private String countryOfBirth;

private String address;
private Date dob;

private List<CommentViewModel> comments = new ArrayList<>();

public class CommentViewModel {

public CommentViewModel(){}

private Long id;
private Date commentDate;
private String comment;
private UserViewModel user;

public class UserViewModel {

public UserViewModel(){}

private Integer id;
private String firstname;
private String surname;
private String email;
        private OrganisationViewModel organisation;

public class OrganisationViewModel {

public OrganisationViewModel(){}

    private Long id;
private String name;

Note I have included Organisation on User (since Organisation is a child of User). I have also included Comment as a List on Person.

Now for the mappers:

@Mapper
public interface PersonMapper {

PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
    @Mapping(source = "person.address.formattedAddress", target = "address")
    PersonViewModel PersonToCommentViewModel(Person person);
}

Note that I only need address as a String.

@Mapper
public interface AgencyCommentMapper {
AgencyCommentMapper INSTANCE = Mappers.getMapper( AgencyCommentMapper.class );
    List<CommentViewModel> CommentToAgencyCommentViewModel(List<Comment> comments);
}

Can someone confirm this @Mapper is needed to map the comments list which is a child to Person.

@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    UserViewModel UserToUserViewModel(User user);
OrganisationViewModel organisationToOrganisationViewModel(Organisations organisation);
Organisations organisationViewModelToOrganisation(OrganisationViewModel organisationViewModel);
}


@Mapper
public interface OrganisationMapper {
OrganisationMapper INSTANCE = Mappers.getMapper( OrganisationMapper.class );
OrganisationViewModel OrganisationToOrganisationViewModel(Organisations organisation);
}


Let me know if this looks correct. Note that most of the mappings are one way at the moment since I am mainly populating a view.

Cheers

Al

Filip Hrisafov

unread,
Sep 21, 2017, 2:31:27 AM9/21/17
to mapstruct-users
Hey Al,

You could do it like that, but then some of your mappers will not be needed. The best way to do this is to tell the mappers that they need to use the other mappers. Use @Mappers(uses = {MyOtherMapper.class}).

So in your case have a look at invoking other mappers in our documentation. In a nutshell you will need to do something like:


@Mapper( uses = {AgencyCommentMapper.class} )
public interface PersonMapper {

PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
@Mapping(source = "person.address.formattedAddress", target = "address")
PersonViewModel PersonToCommentViewModel(Person person);
}

@Mapper( uses = {UserMapper.class} )
public interface AgencyCommentMapper {
AgencyCommentMapper INSTANCE = Mappers.getMapper( AgencyCommentMapper.class );
List<CommentViewModel> CommentToAgencyCommentViewModel(List<Comment> comments);
}

@Mapper( uses = {OrganisationMapper.class} )
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    UserViewModel UserToUserViewModel(User user);
OrganisationViewModel organisationToOrganisationViewModel(Organisations organisation);
Organisations organisationViewModelToOrganisation(OrganisationViewModel organisationViewModel);
}

Al Grant

unread,
Sep 21, 2017, 2:41:26 AM9/21/17
to mapstruct-users
Thanks Filip.

So would that Mapper with all the other Mappers go on the root object? (Which in this case is Person).

Cheers

AL

Filip Hrisafov

unread,
Sep 21, 2017, 2:41:42 PM9/21/17
to mapstruct-users
I don't completely understand what you mean.

Based on the structure you posted, I posted how your mappers should look like. If you just use the PersonMapper like I showed you it would use the other mapper, which then would use the others and etc

Al Grant

unread,
Sep 21, 2017, 2:44:55 PM9/21/17
to mapstruct-users
I wanted to check it was person mapper which it is. Thanks
Message has been deleted

Al Grant

unread,
Sep 22, 2017, 3:13:05 AM9/22/17
to mapstruct-users
As a follow up to this post. I did get things configuered the way Filip suggested.

Two notes. Let say you have Person,and Person has Address. The Mapper on Person is wired like so:

@Mapper( uses = {AddressMapper.class, AgencyCommentMapper.class} )

public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
    PersonAgencyCommentViewModel PersonToAgencyCommentViewModel(Person person);
PersonViewModel PersonToPersonViewModel(Person person);
Person PersonViewModelToPerson(PersonViewModel personViewModel);
}

However you must change your DTO's too so that

PersonDTO {

    private Address address

becomes 

PersonDTO {
    
    private AddressDTO address

etc.

Al
Reply all
Reply to author
Forward
0 new messages