MapStruct generic Map and map combined types list of children objects

3,977 views
Skip to first unread message

almot...@capellasolutions.com

unread,
Oct 2, 2016, 4:52:14 AM10/2/16
to mapstruct-users

Hi, since I not got any answer in stackoverflow, i hope get some answer here :


I have as parent class : User.java , and 2 classes : FacebookUser.java and TwitterUser.javathey are entities that returned depends on the type column in database using DiscriminatorColumn, I want to write correct mapper to map User that could be instance of FacebookUser or TwitterUser. I have the following mapper that seems not works as intended, only Mapping the User parent not the children:

@Mapper
public interface UserMapper {
    public static UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    User map(UserDTO userDTO);

    @InheritInverseConfiguration
    UserDTO map(User user);

    List<UserDTO> map(List<User> users);

    FacebookUser map(FacebookUserDTO userDTO);

    @InheritInverseConfiguration
    FacebookUserDTO map(FacebookUser user);

    TwitterUser map(TwitterUserDTO userDTO);

    @InheritInverseConfiguration
    TwitterUserDTO map(TwitterUser user);

}

Then I use :

UserDTO userDto = UserMapper.INSTANCE.map(user);

Classes to map:

@Entity
@Table(name = "users")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING, length = 10)
@DiscriminatorValue(value = "Local")
public class User {
    @Column
    private String firstName;
    @Column
    private String lastName;
    ///... setters and getters
}

@Entity
@DiscriminatorValue(value = "Facebook")
public class FacebookUser extends User {
    @Column
    private String userId;
    ///... setters and getters
}

@Entity
@DiscriminatorValue(value = "Twitter")
public class TwitterUser extends User {
    @Column
    private String screenName; 
    ///... setters and getters
}

The DTOs:

public class UserDTO {
    private String firstName;
    private String lastName;
    ///... setters and getters
}

public class FacebookUserDTO extends UserDTO {
    private String userId;
    ///... setters and getters
}

public class TwitterUserDTO extends UserDTO {
    private String screenName; 
    ///... setters and getters
}

Also if I have list of users that mixed with Facebook users and Twitter users, or basic user:

Lets say I have the following users:

User user = new User ("firstName","lastName");
User fbUser = new FacebookUser ("firstName","lastName","userId");
User twUser = new TwitterUser ("firstName","lastName","screenName");

List<User> users = new ArrayList<>();
users.add(user);
users.add(fbUser);
users.add(twUser);

//Then: 

List<UserDTO> dtos = UserMapper.INSTANCE.map(users);

I get only firstName and lastName but not screenName or userId.

Any solution for this?

Andreas Gudian

unread,
Oct 2, 2016, 5:16:50 AM10/2/16
to almot...@capellasolutions.com, mapstruct-users
Hi,

You would need to implement the methods that map from User to UserDTO and vice versa yourself by chrcking the actual type with instanceof and delegating to the (generated) more specific method. You can do that with a default-method in the interface or by changing your mapper type to be a abstract class.

We have already drafted this as a feature that MapStruct could provide out of the box and the sketched generated code is basically what you would write manually now:
https://github.com/mapstruct/mapstruct/issues/131 - I guess there are some edge cases that need to be defined, but pull-requests are welcome anytime :-).

Andreas
--
You received this message because you are subscribed to the Google Groups "mapstruct-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mapstruct-users+unsubscribe@googlegroups.com.
To post to this group, send email to mapstruct-users@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

almot...@capellasolutions.com

unread,
Oct 2, 2016, 5:51:42 AM10/2/16
to mapstruct-users, almot...@capellasolutions.com
Hello, 

So you do you mean something like this? :

default UserDTO mapChild(User user) {
if (user instanceof FacebookUser) {
return this.map((FacebookUser) user);
} else if (user instanceof TwitterUser) {
return this.map((TwitterUser) user);
} else {
return this.map(user);
}
}

default User mapChild(UserDTO userDTO) {
if (userDTO instanceof FacebookUserDTO) {
return this.map((FacebookUserDTO) userDTO);
} else if (userDTO instanceof TwitterUserDTO) {
return this.map((TwitterUserDTO) userDTO);
} else {
return this.map(userDTO);
}
}

And if this good, how to handle list of items?
Do I need to write my own method and iterate the list ?

Like:

default List<UserDTO> mapChildren(List<User> users) {
List<UserDTO> dtos = new ArrayList<>();
users.forEach(user -> dtos.add(mapChild(user)));
return dtos;
}

Andreas Gudian

unread,
Oct 2, 2016, 5:54:54 AM10/2/16
to almot...@capellasolutions.com, mapstruct-users
Exactly. For the list variant you don't need to do anything as the generated fist mapping method delegates to your User/UserDTO method. Just check the generated code 👍

almot...@capellasolutions.com

unread,
Oct 2, 2016, 8:29:46 AM10/2/16
to mapstruct-users, almot...@capellasolutions.com
Ok I got another issue but solved it with create empty child (LocalUser) class and (UserDTO.LocalUserDTO) , not really comfort about this, but if you have another suggestion let me know please, the problem I need map children and parent case, if i have 3 types, 1 is parent , or 2 is on of children, the following code for my current solution:


@Mapper(config = CentralConfig.class, uses = {UserCardMapper.class})

public interface UserMapper {
public static UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    LocalUser map(UserDTO.LocalUserDTO userDTO);

@InheritInverseConfiguration
UserDTO.LocalUserDTO map(LocalUser user);


List<UserDTO> map(List<User> users);

    FacebookUser map(UserDTO.FacebookUserDTO userDTO);

@InheritInverseConfiguration
UserDTO.FacebookUserDTO map(FacebookUser user);

TwitterUser map(UserDTO.TwitterUserDTO userDTO);

@InheritInverseConfiguration
UserDTO.TwitterUserDTO map(TwitterUser user);

default UserDTO map(User user) {

if (user instanceof FacebookUser) {
return this.map((FacebookUser) user);
} else if (user instanceof TwitterUser) {
return this.map((TwitterUser) user);
} else {
            return this.map((LocalUser) user);
}
}

@InheritInverseConfiguration
default User map(UserDTO userDTO) {
if (userDTO instanceof UserDTO.FacebookUserDTO) {
return this.map((UserDTO.FacebookUserDTO) userDTO);
} else if (userDTO instanceof UserDTO.TwitterUserDTO) {
return this.map((UserDTO.TwitterUserDTO) userDTO);
} else {
return this.map((UserDTO.LocalUserDTO) userDTO);
}
}

}

I have most updated structure for mapstruct with play project here : https://github.com/almothafar/play-with-mapstruct 
You can compile it with command "activator clean update compile" 

P.S: not related to this post, but may you see warning messages when you compile the project, do you know how to solve it, its so minor, but will be helpful for debugger, because in debug I can't do break points in generated classes or sources.
Reply all
Reply to author
Forward
0 new messages