Search code examples
javaoopinheritancepolymorphismmapstruct

MapStruct generic Map and map combined list of children objects


I have as parent class : User.java , and 2 classes : FacebookUser.java and TwitterUser.java they 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?


Solution

  • Currently, it seems it's not available yet as a feature for mapstruct : Support for Type-Refinement mapping (or Downcast Mapping)

    I asked the question in their google group: https://groups.google.com/forum/?fromgroups#!topic/mapstruct-users/PqB-g1SBTPg

    and found that I need to do manual mapping using default method inside interface (for java 8).

    And got another issue for mapping parent that was almost not applicable so I write one more empty class that child of parent class called LocalUserDTO:

    So the code becomes like the following:

    @Mapper
    public interface UserMapper {
        public static UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    
        LocalUser map(LocalUserDTO userDTO);
    
        @InheritInverseConfiguration
        LocalUserDTO map(LocalUser 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);
    
        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 FacebookUserDTO) {
                return this.map((FacebookUserDTO) userDTO);
            } else if (userDTO instanceof TwitterUserDTO) {
                return this.map((TwitterUserDTO) userDTO);
            } else {
                return this.map((LocalUserDTO) userDTO);
            }
        }
    
    }