Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapstruct - Ambiguous mapping methods found for mapping property

Tags:

java

mapstruct

I am using mapstruct to map from one DTO to another. I have multiple default methods , but 2 of them with a return value of String and that uses the same class as the input parameter gives me "Ambiguous mapping methods using java Mapstruct" error. I am adding the relevant parts of the code here:

@Mappings({
     @Mapping(source = "programInstance", target = "title", qualifiedByName = "title"),
     @Mapping(source = "programInstance", target = "seriesName", qualifiedByName = "seriesName"),
     @Mapping(source = "programInstance", target = "season", qualifiedByName = "season"),
     @Mapping(source = "programInstance", target = "epNumber", qualifiedByName = "epNumber"),
 })
 DTO1 mapDTOs (DTO2 dto2);

  @Named("title")
default String mapTitle(Program programInstance) {
    Optional<String> title = Utils.getObject(() -> programInstance.getTitle().getDescriptions().get(0).getValue());
    if (title.isPresent())
        return title.get();
    return null;
}
@Named("seriesName")
default String mapSeriesName(Program programInstance) {
    Optional<String> seriesName = Utils.getObject(() -> programInstance.get(0).getProgram().getTitle().getDescriptions().get(0).getValue());
    if (seriesName.isPresent())
        return seriesName.get();
    return null;
}
 @Named("season")
default Integer mapSeasonNumber(Program programInstance) {
    Optional<Integer> season = Utils.getObject(() -> programInstance.get(0).getSeasonOf().get(0).getOrderNo());
    if (season.isPresent())
        return season.get();
    return null;
}

@Named("epNumber")
default Integer mapEpNumber(Program programInstance) {
    Optional<Integer> epNumber = Utils.getObject(() -> programInstance.getEpOf().get(0).getOrderNo());
    if (epNumber.isPresent())
        return epNumber.get();
    return null;
}

The error is

Ambiguous mapping methods found for mapping property "Program programInstance" to java.lang.String: java.lang.String mapTitle(), java.lang.String mapSeriesName().

like image 622
ljs Avatar asked Feb 11 '19 03:02

ljs


3 Answers

I was facing same issue and observed that, there was same method inherited by my mapper class using @Mapper(uses = {BaseMapper.class}) and using extends BaseMapper. Removing extends solved the problem for me. So, you can look for method received by custom mapper through multiple ways.

like image 90
Sandip Wagh Avatar answered Nov 03 '22 00:11

Sandip Wagh


I checked your example.. The problem is that the fields you try to target are of type String.

So:

public class IvpVodOfferStatusDTO {

    private String seasonNumber;
    private String episodeNumber;
} 

MapStruct tries to match this with the signature you provide:

 @Named("season")
default Integer mapSeasonNumber(Program programInstance) {
    Optional<Integer> season = Utils.getObject(() -> programInstance.get(0).getSeasonOf().get(0).getOrderNo());
    if (season.isPresent())
        return season.get();
    return null;
}

@Named("epNumber")
default Integer mapEpNumber(Program programInstance) {
    Optional<Integer> epNumber = Utils.getObject(() -> programInstance.getEpOf().get(0).getOrderNo());
    if (epNumber.isPresent())
        return epNumber.get();
    return null;
}

MapStruct has a predefined order of attempts:

  1. User provided Mapping method
  2. Direct (types source -target are the same)
  3. Mapping method (built-in)
  4. Type conversion

If this all fails MapStruct tries to do a number of 2 step approaches:

  1. mapping method - mapping method
  2. mapping method - type conversion
  3. type conversion - mapping method

At 6. it finds 2 qualifying methods (Program to String). It's probably an error in MapStruct that it selects methods that do not qualify (need to check whether this is intentional) by the @Named. Otherwise, I'll write an issue.

The most easy solution is: adapt the target:

public class IvpVodOfferStatusDTO {

    private Integer seasonNumber;
    private Integer episodeNumber;
}

What is probably what you intend (I guess).. Otherwise you could change the signature not to return an Integer but a String

like image 22
Sjaak Avatar answered Nov 03 '22 01:11

Sjaak


Even if the data types are matching, this could happen if the name given at qualifiedByName is not defined in as a bean instance

Because without a matching @Named qualifier, the injector would not know which bean to bind to which variable

@Mapping( source = "firstName", target = "passenger.firstName", qualifiedByName = "mapFirstName" )
public abstract Passenger mapPassenger( Traveller traveller );

@Named( "mapFirstName" )
String mapFirstName( String firstName)
{
}
like image 24
sabeena kumarawadu Avatar answered Nov 02 '22 23:11

sabeena kumarawadu