Search code examples
javaspringautomappermapstruct

Common Aproaches to reduce code-overhead when using MapStruct in Spring Boot


In c# the library https://docs.automapper.org/en/stable/Getting-started.html exists. It allows us to specify all Mappers/Mappings in one File - MapStruct forces me to create a new interface for every Mapper, which results in a lot of files i have to create and clutters the project.

Is there any way to write these mappers in a way that they are all in one file at least, or that they can be configured programatically? I looked at alternatives like ModelMapper or JMaper, but they are not well maintained or way slower.

EDIT: i added sample code to illustrate the problem and highlight my issues with the current answer

public abstract class ProductMapper {
  public abstract ProductDto convertToDto(Product Product);

  public abstract Product convertToEntity(ProductDto checkoutProductDto);

  public abstract List<ProductDto> convertToDtos(List<Product> entities);

  public abstract List<Product> convertToEntities(List<ProductDto> dtos);
// below not working because of type erasure, so cant do it in the same file
  public abstract List<ProductOrderDetails> convertToEntities(List<ProductOrderDetailsDto> entities);

My project has hundreds of DTOs, most of them just requiring these 4 methods and no AfterMapping. Making these by hand is a giant waste of time, are there any better approaches?

Edit: i got a better answer from the mapstruct devs: https://github.com/mapstruct/mapstruct/issues/3111


Solution

  • Creating an abstract class with the 4 base methods, but with generic types seems like it would solve the issue. You'd still need to create the interface manually but the 4 methods would be already defined.

    Here's an example in kotlin of what I mean.

    These classes and mappers ( I've used kotlin only for convenience, but the mappers are still generated in java ) :

    package mapper
    
    import org.mapstruct.Mapper
    
    abstract class EntityMapper<A, B>() {
    
        abstract fun toDto(a: A): B
        abstract fun toEntity(b: B): A
    }
    
    data class Device(val id: String)
    data class DeviceDto(val id: String)
    
    @Mapper
    abstract class DeviceMapper : EntityMapper<Device, DeviceDto>()
    
    data class OtherDevice(val id: String)
    data class OtherDeviceDto(val id: String)
    
    @Mapper
    abstract class OtherDeviceMapper : EntityMapper<OtherDevice, OtherDeviceDto>()
    

    Make mapstruct generate these 2 following mappers:

    @Component
    public class DeviceMapperImpl extends DeviceMapper {
    
        @Override
        public DeviceDto toDto(Device a) {
            if ( a == null ) {
                return null;
            }
    
            String id = null;
    
            id = a.getId();
    
            DeviceDto deviceDto = new DeviceDto( id );
    
            return deviceDto;
        }
    
        @Override
        public Device toEntity(DeviceDto b) {
            if ( b == null ) {
                return null;
            }
    
            String id = null;
    
            id = b.getId();
    
            Device device = new Device( id );
    
            return device;
        }
    }
    

    And

    @Component
    public class OtherDeviceMapperImpl extends OtherDeviceMapper {
    
        @Override
        public OtherDeviceDto toDto(OtherDevice a) {
            if ( a == null ) {
                return null;
            }
    
            String id = null;
    
            id = a.getId();
    
            OtherDeviceDto otherDeviceDto = new OtherDeviceDto( id );
    
            return otherDeviceDto;
        }
    
        @Override
        public OtherDevice toEntity(OtherDeviceDto b) {
            if ( b == null ) {
                return null;
            }
    
            String id = null;
    
            id = b.getId();
    
            OtherDevice otherDevice = new OtherDevice( id );
    
            return otherDevice;
        }
    }