Kathelan
Kathelan

Reputation: 47

Troubleshooting DTO to Entity Mapping in Java with MapStruct

I'm working on a Java project using the MapStruct library and I'm facing challenges with DTO to entity mapping. My goal is to separate the mapping responsibility for different DTOs and mapper classes.

In my example, I have UserDto which contains ContactDto, and ContactDto contains AddressDto. Ultimately, the User entity includes all these fields. I'm aiming for a UserMapper that maps only the UserDto fields, and similarly for other DTOs.

@Mapper(componentModel = "spring",
        unmappedTargetPolicy = ReportingPolicy.IGNORE,
        uses = AddressMapper.class
)
public abstract class ContactMapper {
    public static final ContactMapper MAPPER = Mappers.getMapper(ContactMapper.class);

    @Mapping(source = "user", target = "address")
    public abstract ContactDto toDto(User user);

}

@Mapper(componentModel = "spring",
        uses = ContactMapper.class,
        unmappedTargetPolicy = ReportingPolicy.IGNORE
)
public abstract class UserMapper {
    public static final UserMapper MAPPER = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "user", target = "contact")
    public abstract UserDto toDto(User user);

}

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2024-01-24T16:45:56+0100",
    comments = "version: 1.5.5.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-8.5.jar, environment: Java 17.0.10 (Amazon.com Inc.)"
)
@Component
public class UserMapperImpl extends UserMapper {

    @Autowired
    private ContactMapper contactMapper;

    @Override
    public UserDto toDto(User user) {
        if ( user == null ) {
            return null;
        }

        UserDto.UserDtoBuilder userDto = UserDto.builder();

        userDto.contact( contactMapper.toDto( user ) );
        userDto.firstName( user.getFirstName() );
        userDto.lastName( user.getLastName() );

        return userDto.build();
    }


I'm currently stuck with reversing this mapping. I'm encountering missing target values in the mapping and I'm unsure how to proceed. I've considered using @AfterMapping or @BeforeMapping, but they introduce unwanted complexity.

I want to avoid verbose mappings like this:

    @Mapping(target = "email", source = "email")
    @Mapping(target = "phoneNumber", source = "phoneNumber")
    @Mapping(source = "addressDto.street", target = "street")
    @Mapping(source = "addressDto.city", target = "city")
    public abstract User toEntity(ContactDto contactDto);

This approach is impractical for larger projects due to the amount of boilerplate code it generates. I'm looking for a more scalable solution.

Has anyone experienced a similar issue with MapStruct? I'm particularly interested in solutions for efficiently reversing the mappings without excessive manual specification. Any tips or alternative approaches would be greatly appreciated!

For a more detailed look at the code, you can view the project on GitHub: MapStruct Inheritance Example.

Upvotes: 0

Views: 1609

Answers (1)

Paul Marcelin Bejan
Paul Marcelin Bejan

Reputation: 1685

Since you don't have entities for each dto, you can't use

@InheritInverseConfiguration

where basically on each mapper, you can have two methods,

  1. toDto
  2. toEntity annotated with @InheritInverseConfiguration will inverse the mapping of the toDto method.

In your case, what you can do is to take all the fields from contact and map it to User, you don't have to map it one by one, you can use "." as target if the fields name match. Same with the fields of address

so your toEntity method will look like that:

@Mapping(target = ".", source = "contact")
@Mapping(target = ".", source = "contact.address")
User toEntity(UserDto userDto);

mapping-nested-bean-properties-to-current-target

P.S. If you don't have mapper fields, you can replace abstract class with interface, example:

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, 
        uses = ContactMapper.class, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {

    UserMapper MAPPER = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "user", target = "contact")
    UserDto toDto(User user);

    @Mapping(target = ".", source = "contact")
    @Mapping(target = ".", source = "contact.address")
    User toEntity(UserDto userDto);

}

Upvotes: 1

Related Questions