How to map extended DTO's from same source class

Steen picture Steen · Feb 1, 2017 · Viewed 9.4k times · Source

I've started using Mapstruct to map JPA entities to DTO's. For basic entities, this works great.

My problem: Some entities have lazy loaded collections, containing additional details, that I don't want always want to fetch and map. As a solution I've added a basic superclass with all fields that are always mapped, and a subclass containing the collections. They both represent the same entity, so they use the same source class.

When I try to create a Mapper containing methods to map to both types from the same source, I get an ambiguous mapping methods error, even though the method signature (at least the return type) is different. Am I going about this the wrong way? Can't I use subclasses for DTO's using the same source?

Edit: In case it matters, I'm using mapstruct-jdk8:1.1.0.Final

Edit 2: The example below was just an example, on the top of my head. When I've actually used the code, it worked. It turns out my issue is with something that wasn't included in the example. It appears that the error occurs when I add a method for mapping a Collection of Tickets. This probably means the issue is not (directly?) related with inheritance. I'm probably missing some configuration, but I'm not sure what to look for.

Simple example:

Ticket entity

public class Ticket {
  private long id;
  private String title;
  private Set<Comment> comments;

  // Getters and setters
}

Ticket DTO

public class TicketDTO {
  private long id;
  private String title;

  // Getters and setters
}

Ticket with comments DTO

public class TicketWithCommentsDTO extends TicketDTO {
  private List<CommentDTO> comments;


  // Getters and setters
}

Ticket Mapper interface

@Mapper(uses= { CommentMapper.class })
public interface TicketMapper {
  TicketDTO mapToTicketDTO(Ticket ticket);

  List<TicketDTO> mapToTicketDTOList(Collection<Ticket> tickets); // Adding this method or the last method causes the error

  TicketWithCommentsDTO mapToTicketWithCommentsDTO(Ticket ticket);

  List<TicketWithCommentsDTO> MapToTicketWithCommentDTOList(Collection<Ticket> tickets); 
}

Comment Mapper interface

@Mapper
public interface CommentMapper {
  CommentDTO toCommentDTO(Comment comment);

  List<CommentDTO> toCommentDTOList(Collection<Comment> comments);
}

The error thrown:

 Ambiguous mapping methods found for mapping collection element to 
 dto.TicketDTO: dto.TicketDTO mapToTicketDTO(model.Ticket ticket), 
 dto.TicketWithCommentsDTO mapToTicketWithCommentsDTO(model.Ticket ticket).

Answer

Steen picture Steen · Feb 2, 2017

Well, this turned out to be a simple fix, it was indeed a missing configuration issue. What was missing was the @IterableMapping annotation.

Once I set the elementTargetType to the correct types, everything worked as expected.

The correct Mapper code

@Mapper(uses = { CommentMapper.class })
public interface TicketMapper {
    TicketDTO mapToTicketDTO(Ticket ticket);

    @IterableMapping(elementTargetType = TicketDTO.class)
    List<TicketDTO> mapToTicketDTOList(Collection<Ticket> tickets);

    TicketWithCommentsDTO mapToTicketWithCommentsDTO(Ticket ticket);

    @IterableMapping(elementTargetType = TicketWithCommentsDTO.class)
    List<TicketWithCommentsDTO> mapToTicketWithCommentDTOList(Collection<Ticket> tickets);
}