ModelMapper: выберите отображение на основе дочернего класса - PullRequest
0 голосов
/ 24 сентября 2018

TL; DR

Я хочу использовать modelMapper таким образом, чтобы я отображал из AbstractParent в AbstractParentDTO, а затем в ModelMapper-Config вызывал конкретные преобразователи для каждого подкласса изатем пропустите остальные сопоставления (класса abstrac).

Как это возможно?Это правильный подход?Есть ли недостатки дизайна?


Что у меня есть:

Родительский объект:

@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type")
public abstract class Parent {
//some more fields
}

Один дочерний объект:

//Basic Lombok Annotations
@DiscriminatorValue("child_a")
public class ChildA extends Parent {
//some more fields
}

Другая дочерняя сущность:

@DiscriminatorValue("child_b")
public class ChildB extends Parent {
//some more fields   
}

Тогда у меня есть родительский класс DTO:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildA.class, name = "child_a"),
@JsonSubTypes.Type(value = ChildB.class, name = "child_b"),
public abstract class ParentDTO {
//some more fields
}

Один дочерний DTO:

public class ClassADTO extends ParentDTO {
//some more fields
}

и другое DTO:

public class ClassBDTO extends ParentDTO {
//some more fields
}

В моем случае я получу DTO от контроллера и сопоставлю их с сущностями при передаче их Сервису.Мне придется делать то же самое в 5-6 конечных точках.

Конечные точки выглядят примерно так:

@PreAuthorize(CAN_WRITE)
@PutMapping("/{id}")
public ResponseEntity<ParentDTO> update(
        @PathVariable("id") UUID id,
        @RequestBody @Valid ParentDTO parentDTO) {

    Parent parent = parentService.update(id, parentDTO);

    if (parentDTO instanceof ChildADTO) {
        return ResponseEntity.ok(modelMapper.map(parent, ChildADTO.class));
    } else if (parentDTO instanceof ChildBDTO) {
        return ResponseEntity.ok(modelMapper.map(parent, ChildBDTO.class));
    }
    throw new BadRequestException("The Parent is not Valid");
}

Только у меня есть еще несколько детей, которые делают вещи еще громоздче.


То, что я хочу:

Вместо того, чтобы много раз проверять, что такое экземпляр DTO (или Entity), я просто хочу написать, например,:

modelmapper.map(parent, ParentDTO.class)

и выполните проверку "экземпляр ..." РАЗ в моей конфигурации ModelMapper.


Что я пробовал:

У меня уже есть разные конвертеры для каждого возможного направления и случая отображения, определенных в моей конфигурации ModelMapper (поскольку они в любом случае требуют более сложного отображения).

Я попытался решить свою проблему, написав еще одинКонвертер для родительских классов и установка его в качестве преконвертера ModelMapper:

    //from Entity to DTO
    Converter<Parent, ParentDTO> parentParentDTOConverter = mappingContext -> {
        Parent source = mappingContext.getSource();
        ParentDTO dest = mappingContext.getDestination();

        if (source instanceof CHildA) {
            return modelMapper.map(dest, ChildADTO.class);
        } else if (source instanceof ChildB) {
            return modelMapper.map(dest, ChildBDTO.class);
        } 
        return null;
    };

и:

modelMapper.createTypeMap(Parent.class, ParentDTO.class)
                .setPreConverter(parentParentDTOConverter);

Но я всегда получаю одну и ту же ошибку MappingError:

1) Не удалось создать экземпляр экземпляра назначения com.myexample.data.dto.ParentDTO.Убедитесь, что com.myexample.data.dto.ParentDTOO имеет частный конструктор без аргументов.

, который я получаю (я полагаю), я не могу создать объект абстрактного класса.Но это не то, что я пытаюсь?Я предполагаю, что modelMapper все еще выполняет остальную часть Mapping после окончания моего PreConverter.Я также пытался установить его с помощью .setConverter, но всегда с тем же результатом.


  • Кто-нибудь знает, как «отключить» пользовательские сопоставления?Я действительно не хочу писать «псевдо-картографы», которые действуют как картографы и просто вызывают определенные картографы для каждого сценария.

  • Мой дизайн просто плох?Как бы вы улучшили его?

  • Это просто еще не реализовано в ModelMapper?

Любая помощь и подсказка приветствуется.

Ответы [ 3 ]

0 голосов
/ 26 сентября 2018

Я бы использовал ObjectMapper вместо ModelMapper.

В Parent классе добавьте возможность получить значение дискриминатора.

//..
public class Parent {

    @Column(name = "type", insertable = false, updatable = false)
    private String type;
    //getters and setters
}

Ваш ParentDTO должен быть сопоставлен сДочерний (*) DTO

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = ChildADTO.class, name = "child_a"),
        @JsonSubTypes.Type(value = ChildBDTO.class, name = "child_b")
})
 public abstract class ParentDTO {
   // ..
 }

в сервис / метод преобразования добавляет объект сопоставления с игнорированием неизвестного (чтобы игнорировать то, что вы не объявили в своем классе DTO)

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

простопросто позвоните:

Parent parent = // get from repository
ParentDTO parentDTO = objectMapper.readValue(objectMapper.writeValueAsBytes(parent), ParentDTO.class);

Таким образом, ваш ParentDTO всегда создается с правильным типом.

0 голосов
/ 28 сентября 2018

Как насчет

    TypeMap<Parent.class, ParentDTO.class> typeMap = modelMapper.createTypeMap(Parent.class, ParentDTO.class);

    typeMap
     .include(ChildA .class, ClassADTO .class)
    .include(ChildB.class, ClassbDTO.class);

ссылка: http://modelmapper.org/user-manual/type-map-inheritance

0 голосов
/ 26 сентября 2018

Ну, решение, которое я нашел, использует конвертеры.В этом случае modelMapper не пытается создать новый экземпляр абстрактного класса, но использует конвертер напрямую.

Вы можете разместить все конвертеры в одном месте

modelMapper.createTypeMap(ChildA.class, ParentDTO.class)
            .setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassADTO.class));

modelMapper.createTypeMap(ChildB.class, ParentDTO.class)
            .setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassBDTO.class));
....
...