MapStruct: Map List of objects, when object is mapped from two objects

37,148

Solution 1

I found how to implement it with decorators, thanks @Gunnar Here is an implementation:

Beans

public class Child {
    int id;
    String name;
}
public class Parent {
    int id;
    String name;
}
public class ChildDto {
    int id;
    String name;
    int parentId;
    String parentName;
}
// getters/settes ommited

Mapper

@Mapper
@DecoratedWith(ChildMapperDecorator.class)
public abstract class ChildMapper {
    public static final ChildMapper INSTANCE = Mappers.getMapper(ChildMapper.class);

    @Mappings({
            @Mapping(target = "parentId", ignore = true),
            @Mapping(target = "parentName", ignore = true)
    })
    @Named("toDto")
    abstract ChildDto map(Child child);

    @Mappings({
            @Mapping(target = "id", ignore = true),
            @Mapping(target = "name", ignore = true),
            @Mapping(target = "parentId", source = "id"),
            @Mapping(target = "parentName", source = "name")
    })
    abstract ChildDto map(@MappingTarget ChildDto dto, Parent parent);

    @IterableMapping(qualifiedByName = "toDto") // won't work without it
    abstract List<ChildDto> map(List<Child> children);

    List<ChildDto> map(List<Child> children, Parent parent) {
        throw new UnsupportedOperationException("Not implemented");
    }
}

Decorator

public abstract class ChildMapperDecorator extends ChildMapper {
    private final ChildMapper delegate;

    protected ChildMapperDecorator(ChildMapper delegate) {
        this.delegate = delegate;
    }

    @Override
    public List<ChildDto> map(List<Child> children, Parent parent) {
        List<ChildDto> dtoList = delegate.map(children);
        for (ChildDto childDto : dtoList) {
            delegate.map(childDto, parent);
        }
        return dtoList;
    }
}

I use abstract class, not interface for mapper, because in case of interface you couldn't exclude for generation method map(List<Child> children, Parent parent), and the code being generated is not valid in compile time.

Solution 2

I used an @AfterMapping as suggested by Gunnar:

@AfterMapping
public void afterDtoToEntity(final QuestionnaireDTO dto, @MappingTarget final Questionnaire entity) {
  entity.getQuestions().stream().forEach(question -> question.setQuestionnaire(entity));
}

This made sure all the questions were linked to the same questionnaire entity. This was the final part of the solution for avoiding the JPA error save the transient instance before flushing on creating a new parent entity with a list of children.

Solution 3

That's not possible out of the box as things stand. You could use a decorator or after-mapping method to set the parent to all the child objects afterwards.

Share:
37,148
AlexB
Author by

AlexB

Updated on June 25, 2021

Comments

  • AlexB
    AlexB almost 3 years

    Assume I have such mapping:

    @Mapping(source = "parentId", target = "parent.id")
    Child map(ChildDto dto, Parent parent);
    

    Now I need to map List of ChildDto to List of Child, but they all have the same parent. I expect to do something like that:

    List<Child> map(List<ChildDto> dtoList, Parent parent);
    

    But it doesn't working. Is there any chance to do it?

  • AlexB
    AlexB almost 8 years
    Please look at my answer: stackoverflow.com/a/37796378/2482449 Does it seems like what you meant?
  • Mehdi Benmesssaoud
    Mehdi Benmesssaoud almost 4 years
    I tried this, and it doesn't work. for my case i have dto with a set of children of the same dto