MapStruct maps the model's field as empty when the field is of an abstract type

24 Views Asked by At

I have a class that has a field which in turn is an abstract class. I would like MapStruct to be able to map from the incoming DTO to the internal model including all the subfields of the abstract class, but I only get the empty object in return —rightfully so, since that's the way I've defined the mapping function—.

How can I make it so that MapStruct not only maps the top class, but also the field's classes? Do I have to create mappers for the Cat and Dog classes too, and then bring them in to the OwnerMapper class?

Thank you in advance!

Example setup

Mapper interface

@Mapper(componentModel = MappingConstants.ComponentModel.CDI)
public interface OwnerMapper {

    @Mapping(source = "pet", target = "pet", qualifiedByName = "mapPet")
    Owner toEntity(OwnerDTO owner);

    @Named("mapPet")
    default Pet mapPet(final PetDTO pet) {
        if (pet instanceof CatDTO) {
            return new Cat();
        } else {
            return new Dog();
        }
    }
}

Internal classes

public class Owner {
    private String name;
    private Pet pet;

    public Owner() { }
    // Omitting getters and setters.
}
public abstract class Pet {
    public abstract String makeNoise();
}
public final class Dog extends Pet {
    private UUID dogTag;
    private String name;

    public Dog() { }

    @Override
    public String makeNoise() {
        return "Bark!";
    }

    // Omitting getters and setters.
}
public final class Cat extends Pet {
    private boolean bell;

    public Cat() { }

    @Override
    public String makeNoise() {
        return "Meow!";
    }

    // Omitting getters and setters.

DTO classes

public class OwnerDTO {
    private String name;
    private PetDTO pet;

    public OwnerDTO() { }

    // Omitting getters and setters.
public abstract class PetDTO {
}
public final class CatDTO extends PetDTO {
    private UUID catTag;
    private boolean bell;

    public CatDTO() { }

    // Omitting getters and setters.
public final class DogDTO extends PetDTO {
    private UUID dogTag;

    private String name;

    public DogDTO() { }

    // Omitting getters and setters.
1

There are 1 best solutions below

0
MikelAlejoBR On BEST ANSWER

Well, I solved it by defining a few more mapping methods and calling them from the named mapper function:

@Mapper(componentModel = MappingConstants.ComponentModel.CDI)
public interface OwnerMapper {

    @Mapping(source = "pet", target = "pet", qualifiedByName = "mapPet")
    Owner toEntity(OwnerDTO owner);

    Cat catToEntity(CatDTO cat);

    Dog dogToEntity(DogDTO dog);

    @Named("mapPet")
    default Pet mapPet(final PetDTO pet) {
        if (pet instanceof CatDTO cat) {
            return this.catToEntity(cat);
        } else if (pet instanceof DogDTO dog) {
            return this.dogToEntity(dog);
        } else {
            throw new IllegalStateException("Unsupported pet type identified");
        }
    }
}