mapping interface field

Пытаюсь замапить BookDTO.java в Book.java используя MapStruct, но сталкиваюсь с ошибкой.

Вот тут все мои DTO:

BookDTO.java:

@Data
@Document
public class BookDTO {

    @Id
    private UUID id;
    private BookSpecificationsDTO specifications;

} 

BookSpecificationsDTO interface:

public interface BookSpecificationsDTO {
    BookSpecificationsTypeDTO type = null;

    default BookSpecificationsTypeDTO getType() {
        return type;
    }
}

BookSpecificationsTypeDTO:

public enum BookSpecificationsTypeDTO{

    CLASSIC,
    PHANTASY
}

ClassicBookSpecificationDTO:

@Data
public class ClassicBookSpecificationDTO implements BookSpecificationsDTO {
    private BookSpecificationsTypeDTO type = BookSpecificationsTypeDTO.CLASSIC;
    private String genre;
    private int length;
}

PhantasyBookSpecificationDTO:

@Data
    public class PhantasyBookSpecificationDTO implements BookSpecificationsDTO {
        private BookSpecificationsTypeDTO type = BookSpecificationsTypeDTO.PHANTASY;
        private String genre;
   }

Тут все мои модели, которые в моем случае являются идентичными DTO:

Book.java:

@Data
@Document
public class Book {

    @Id
    private UUID id;
    private BookSpecifications specifications;

}

BookSpecifications interface:

public interface BookSpecifications {
    BookSpecificationsType type = null;

    default BookSpecificationsType getType() {
        return type;
    }
}

BookSpecificationsType:

public enum BookSpecificationsType{

    CLASSIC,
    PHANTASY
}

ClassicBookSpecification:

@Data
public class ClassicBookSpecification implements BookSpecifications {
    private BookSpecificationsType type = BookSpecificationsType.CLASSIC;
    private String genre;
    private int length;
}

PhantasyBookSpecification:

@Data
    public class PhantasyBookSpecification implements BookSpecifications {
        private BookSpecificationsType type = BookSpecificationsType.PHANTASY;
        private String genre;
   }

Я хотел замапить свой BookDTO.java в Book.java model. Вот тут mapper class:

@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface BookMapper {
        
    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID())")
    Book toBook(BookDTO bookDTO);

}

Вот тут ошибки, кторые я получаю:

java: The return type BookSpecificationsDTO is an abstract class or interface. Provide a non abstract / non interface result type or a factory method.

и

java: Can't map property "BookSpecificationsDTO specifications" to "BookSpecifications specifications". Consider to declare/implement a mapping method: "BookSpecifications map(BookSpecificationsDTO value)".

Насколько я понял, проблема заключается в том, что в классе Book и BookDTO находится поле BookSpecifications, которое является интерфейсом, имеет тип ClassicBookSpecification или PhantasyBookSpecification в зависимости от введенного параметра type, и MapStruct не может замапить поле, которое является итерфейсом. Возможно ли как-то данный маппинг реализовать?


Ответы (1 шт):

Автор решения: tequillaz

Вообще, так как тут необходима информация времени выполнения, не существует простого и одновременно "чистого" способа решить эту задачу.

"Грязный" способ заключается в использовании рефлексии. Вы можете сделать следующее:

  1. для каждой пары классов-наследников определить свой собственный метод трансформации;
  2. определить общий метод трансформации интерфейсов, в котором в зависимости от типа переданного параметра дергать тот или иной метод из группы (1)

Выглядеть это может примерно так:

@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface BookMapper {
        
    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID())")
    Book toBook(BookDTO bookDTO);

    /**
     * ...
     */
    ClassicBookSpecification toBookSpecification(ClassicBookSpecificationDTO specDTO);

    /**
     * ...
     */
    PhantasyBookSpecification toBookSpecification(PhantasyBookSpecification specDTO);

    /**
     * ...
     */
    default BookSpecifications toBookSpecificationGeneric(BookSpecificationsDTO specDTO) {
        if(specDTO == null) {
           return null;
        }

        switch(specDTO.getType()) {
            case CLASSIC:
                return toBookSpecification((ClassicBookSpecification)specDTO);
            case PHANTASY:
                return toBookSpecification((PhantasyBookSpecification)specDTO);
        }

        throw new InvalidArgumentException(specDTO.getType());
    }

}

Второй способ - способ без рефлексии, - это написание маппера-визитера.

public interface BookSpecifications {
    BookSpecificationsType type = null;

    default BookSpecificationsType getType() {
        return type;
    }

    BookSpecification toBookSpecification(BookMapper mapper);
}

// ...

@Data
public class ClassicBookSpecificationDTO implements BookSpecificationsDTO {
    
    // ...

    public BookSpecifications toBookSpecification(BookMapper mapper) {
        return mapper.toBookSpecification(this);
    }

}

// ...

@Data
public class PhantasyBookSpecificationDTO implements BookSpecificationsDTO {
    
    // ...

    public BookSpecifications toBookSpecification(BookMapper mapper) {
        return mapper.toBookSpecification(this);
    }

}

// ...

@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface BookMapper {
        
    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID())")
    Book toBook(BookDTO bookDTO);

    /**
     * ...
     */
    ClassicBookSpecification toBookSpecification(ClassicBookSpecificationDTO specDTO);

    /**
     * ...
     */
    PhantasyBookSpecification toBookSpecification(PhantasyBookSpecification specDTO);

    /**
     * ...
     */
    default BookSpecifications toBookSpecificationGeneric(BookSpecificationsDTO specDTO) {
        return specDTO.toBookSpecification(this);
    }

}

Плюсы и минусы у каждого подхода - свои, а применимость должна выбираться исходя из конкретной задачи

→ Ссылка