Mapping Hierarchy of Classes with Mapstruct
Asked Answered
E

3

7

I have a hierarchy of classes: VehicleDTO is a base abstract class. CarDTO, TruckDTO, VanDTO extend from it.

I have the same hierarchy on the other side of a mapper: VehicleBO <- CarBO, TruckBO, VanBO.

I want to have all the mapping logic consolidated in one mapper. Period.

I have defined mappings for common attributes, but here is when it becomes interesting, I get this exception during compilation:

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

So, how do I specify a factory method, that based on a value of a particular attribute or a class of the pojo, would create a target object for me? I would appreciate a good code snippet that actually does the trick.

Thanks!

Ewing answered 23/6, 2020 at 21:37 Comment(1)
How does your mapper look like?Terrorist
A
6

You need to set the subclassExhaustiveStrategy property in your @Mapper annotation to RUNTIME_EXCEPTION.

See Mapstruct documentation:

...

To allow mappings for abstract classes or interfaces you need to set the subclassExhaustiveStrategy to RUNTIME_EXCEPTION, you can do this at the @MapperConfig, @Mapper or @BeanMapping annotations. If you then pass a GrapeDto an IllegalArgumentException will be thrown because it is unknown how to map a GrapeDto. Adding the missing (@SubclassMapping) for it will fix that.

...
Amazonas answered 21/10, 2022 at 14:27 Comment(0)
T
2

You can use a method annotated with @ObjectFactory receiving a source parameter for what you need.

Let's assume that you have a mapper that looks like:

@Mapper
public interface VehicleMapper {

    VehicleDTO map(VehicleBO vehicle);

    // more
}

If you add a method looking like:

@ObjectFactory
default VehicleDTO createVehicleDto(VehicleBO vehicle) {
    // your creation logic
}

Then MapStruct will use the createVehicleDto to create the VehicleDTO object.

NOTE when mapping hierarchies and when the mapping looks like the one in the answer then MapStruct will only map the properties which are in the VehicleDTO class and not in possible implementations of the class. The reason for that is that MapStruct generates the mapping code during compilation and not during runtime.

For mapping hierarchies like what you explained you can do something like the following:

public interface VehicleMapper {

    default VehicleDTO map(VehicleBO vehicle) {
        if (vehicle instanceOf CarBO) {
            return map((CarBO) vehicle);
        } else if (vehicle instanceOf TruckBO) {
            return map((TruckBO) vehicle);
        } else if (vehicle instanceOf VanBO) {
            return map((VanBO) vehicle);
        } else {
            //TODO decide what you want to do
        }
    }

    @Named("car")
    CarDTO map(CarBO car);

    @Named("truck")
    TruckDTO map(TruckBO truck);

    @Named("car")
    VanDTO map(VanBO van);

    // more
}

There is mapstruct/mapstruct#131 requesting for generating code like my example out of the box

Terrorist answered 24/6, 2020 at 5:27 Comment(4)
I have a follow-up question, you are mentioning that only common fields will be mapped, can I use a bunch of methods with @MappingTarget annotation to help me set subclass specific fields?Ewing
You don't need @MappingTarget. You can also define dedicated methods for the mapping between different types. You can make the VehicleDTO map(VehicleBO vehicle) and do instance of checks and delegate to other methodsTerrorist
If I make a method like you are suggesting: "You can make the VehicleDTO map(VehicleBO vehicle) and do instance of checks and delegate to other methods" what is the point of using mapstruct? Maybe I am not getting where you are getting at.Ewing
Look at my edit. MapStruct is an annotation processor generating code during compile time. It doesn't know about the possible types of your obejcts during runtimeTerrorist
H
0

Nowadays, maybe using Visitor pattern could be better choice instead of the instanceOf way, check below:

https://techlab.bol.com/en/blog/mapstruct-object-hierarchies

Horvath answered 11/5, 2022 at 8:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.