10

I have two types of data that I want to map:

SignUpUserDto:

public class SignUpUserDto {
    private String firstName;
    private String lastName;
    private String username;
    private String email;
    private String password;
    private String title;
}

SignUpUser:

@Entity
public class SignUpUser {
    private Long id;
    private String firstName;
    private String lastName;
    private String username;
    private String email;
    private String password;
    private Title title;
}

Title:

public enum Title {
    JUNIOR("junior"),
    MIDDLE("middle"),
    SENIOR("senior"),
    MANAGER("manager");

    private final String title;

    Title(final String title) {
        this.title = title;
    }

    public String toString() {
        return this.title;
    }
}
  • For DTO title member is a String.

  • For entity title member is a Title.

How should the mapper looks like?

Should I pass title already converted in Service?

@Mapper(componentModel = "spring")
public interface SignUpUserMapper {
    SignUpUserMapper INSTANCE = Mappers.getMapper(SignUpUserMapper.class);
    @Mapping(target = "title", expression = "title")
    public SignUpUserDto signUpUserToSignUpUserDto(SignUpUser signUpUser, String title);
    @Mapping(target = "title", source = "title")
    public SignUpUser signUpUserDtoToSignUpUser(SignUpUserDto signUpUserDto, Title title);
}

Or should I do conversion in Mapper?

@Mapper(componentModel = "spring",  imports = Title.class)
public interface SignUpUserMapper {
    SignUpUserMapper INSTANCE = Mappers.getMapper(SignUpUserMapper.class);
    @Mapping(target = "title", expression = "java(signUpUser.getTitle().toString())")
    public SignUpUserDto signUpUserToSignUpUserDto(SignUpUser signUpUser);
    @Mapping(target = "title", source = "java(new Title(signUpUserDto.getTitle()))")
    public SignUpUser signUpUserDtoToSignUpUser(SignUpUserDto signUpUserDto);
}
Robert
  • 186
  • 3
  • 6
  • 16

2 Answers2

10

Should I pass title already converted in Service?

You definitely shoul NOT do it. It is converter's job, not service's

Try following approach:

1) Add conversion method to enum class

enum Title {
    ...

    public static Title fromString(String title) {
        if (title != null) {
            for (Title t : Title.values()) {
                if (t.toString().equals(title)) {
                    return t;
                }
            }
        }
        return null;
    }
}

2) Add 2 conversion methods to Mapper interface (Java 8+ only)

@Mapper(componentModel = "spring")
public interface SignUpUserMapper {
    SignUpUserDto signUpUserToSignUpUserDto(SignUpUser signUpUser);
    SignUpUser signUpUserDtoToSignUpUser(SignUpUserDto signUpUserDto);

    default String fromEnum(Title title) {
        return title == null ? null : title.toString();
    }

    default Title toEnum(String title) {
        return title == null ? null : Title.fromString(title);
    }
}
Nikolai Shevchenko
  • 7,083
  • 8
  • 33
  • 42
  • I've seen in documentation one more option with an abstract class instead of interface. What is the difference between this two approaches? Can you give me some use cases? Thank you! – Robert Aug 01 '19 at 10:13
  • 1
    You should use abstract class instead of interface when you need to autowire some bean(s) into mapper – Nikolai Shevchenko Aug 01 '19 at 10:20
  • What is the approach for Java 8- ? Why is not a good approach using imports and @Mapping annotations? Should I include repositories in Mappers with autowire and retrieve data there or should I do this in Service? – Robert Aug 01 '19 at 10:22
  • 1
    Same approach for Java 8-, but since there are no default interface methods in Java 8-, you will use abstract classes instead – Nikolai Shevchenko Aug 01 '19 at 10:24
  • 2
    One addition: I think conversion to string is done automatically by mapstruct, hence you should not have to add a method for that. The conversion from string could be done by a backing map wich is probably more efficient. – Sjaak Aug 01 '19 at 16:08
  • @Sjaak I believe this approach with an explicit `fromString()` method is useful for different types of data, for example `Integer` with method `fromInteger()` – Robert Aug 03 '19 at 12:05
  • @robert I don't question the need for a fromString itself but merely the implementation.. that could be done IMHO a look in a static initialized map rather than in a loop. – Sjaak Aug 03 '19 at 17:53
3

Use the second option like this:

 @Mapper(componentModel = "spring",  imports = Title.class)                     
public interface SignUpUserMapper {
SignUpUserMapper INSTANCE = Mappers.getMapper(SignUpUserMapper.class);
@Mapping(target = "title", expression = "java(signUpUser.getTitle().toString())")
public SignUpUserDto signUpUserToSignUpUserDto(SignUpUser signUpUser);
@Mapping(target = "title", source = "java(Title.valueOf(signUpUserDto.getTitle().toUpperCase()))")
public SignUpUser signUpUserDtoToSignUpUser(SignUpUserDto signUpUserDto);            
}
Daoud Shaheen
  • 364
  • 4
  • 16