Issue
Initial situation
Using current 1.5.0.Beta2 MapStruct release with JDK 13.
Domain Model
class Wrapper {
private Fruit fruit;
}
abstract class Fruit {
private int weight;
/* ... */
}
class Apple extends Fruit {
/* ... */
}
class Banana extends Fruit {
/* ... */
}
(Corresponding 1:1 DTOs omitted)
Mappers
Mapper for Wrapper class
@Mapper(uses = {FruitMapper.class})
public interface WrapperMapper {
WrapperDto map(Wrapper wrapper)
}
Mapper for Fruit class(es)
MapStruct Mapper
for abstract Fruit
class with method signature and annotations:
@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION /*...*/)
public interface FruitMapper {
@SubclassMapping(source = Apple.class, target = AppleDto.class)
@SubclassMapping(source = Banana.class, target = BananaDto.class)
FruitDto map(Fruit fruit);
}
Problem
The above works fine until a field needs to be ignored on the referenced abstract class (e.g. the weight
of a Fruit
).
Putting this annotation to the WrapperMapper
map
method...
@Mapping(target = "fruit.weight", ignore = true)
WrapperDto map(Wrapper wrapper)
...leads to The return type FruitDto is an abstract class or interface. Provide a non abstract / non interface result type or a factory method.
compile error.
Question
Is there a way to skip fields in MapStruct mapping as outlined without getting this compile error?
Solution
Cause
What happens is that because you designate it on the WrapperMapper it will not directly use the FruitMapper. Instead the WrapperMapper implementation will get a new mapping method generated to map fruits. MapStruct fails at this attempt and gives you that compile error.
Solution
If you add that ignore to the FruitMapper it would inherit it to both subclassmappings:
@SubclassMapping( source = Apple.class, target = AppleDto.class )
@SubclassMapping( source = Banana.class, target = BananaDto.class )
@Mapping( target = "weight", ignore = true )
FruitDto map( Fruit fruit );
In case you want to have a mapping with weights and one without weights you could add the @org.mapstruct.Named
annotation to the one without weights and then use this at the WrappedMapper.
like this:
@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION /*...*/)
public interface FruitMapper {
@SubclassMapping( source = Apple.class, target = AppleDto.class )
@SubclassMapping( source = Banana.class, target = BananaDto.class )
FruitDto map( Fruit fruit );
@SubclassMapping( source = Apple.class, target = AppleDto.class )
@SubclassMapping( source = Banana.class, target = BananaDto.class )
@Mapping( target = "weight", ignore = true )
@Named( "NoWeights" )
FruitDto map( Fruit fruit );
}
@Mapper(uses = {FruitMapper.class})
public interface WrapperMapper {
@Mapping( target = "fruit", qualifiedByName = "NoWeights" )
WrapperDto map(Wrapper wrapper)
}
Answered By - Ben Zegveld