3

I map classes to DTO with MapStruct. But I need to pass parent object to child mapper because I have to use values from parent for mapping child.

I try to pass parent with @Context:

@Mapping(target = "valueWithName", source = "child", qualifiedByName = "getValueWithName")
ChildDto map(Child child, @Context Parent parent);

@Named("getValueWithName")
default String getValueWithName(Child child, @Context Parent parent) {
    return child.getValue() + parent.getName();
}

But it cause compilation error:

Example.java:44:19
java: Unmapped target property: "valueWithName". Mapping from Collection element "Example.Child children" to "Example.ChildDto children".

Here is simple full working example:

public class Example {

    @Data
    @AllArgsConstructor
    public static class Parent {
        private String name;
        private List<Child> children;
    }

    @Data
    @AllArgsConstructor
    public static class Child {
        private String value;
    }

    @Data
    @AllArgsConstructor
    public static class ParentDto {
        private String name;
        private List<ChildDto> children;
    }

    @Data
    @AllArgsConstructor
    public static class ChildDto {
        private String valueWithName;
    }

    @Mapper
    public interface MyMapper {

        ParentDto map(Parent parent);

        @Mapping(target = "valueWithName", source = "child", qualifiedByName = "getValueWithName")
        ChildDto map(Child child);

        @Named("getValueWithName")
        default String getValueWithName(Child child) {
            return child.getValue() + "name from parent"; // TODO
        }
    }

    public static void main(String[] args) {
        MyMapper mapper = Mappers.getMapper(MyMapper.class);
        Parent parent = new Parent("a", List.of(new Child("b")));
        ParentDto parentDto = mapper.map(parent);

        System.out.println(parentDto.getName()); // a
        System.out.println(parentDto.getChildren().get(0).getValueWithName()); // expect: ba
    }
}

I saw similar question with answer but I think it's different case or I don't know how to use @AfterMapping in my case (notice that child.getValue() + parent.getName() is only example, I need to use many fields from parent, so I need to access to parent when mapping child; and also I have list of children).

MapStruct version 1.5.3.

mkczyk
  • 2,460
  • 2
  • 25
  • 40

1 Answers1

0

As mentioned in the referenced answer, you could solve it in two ways described below.

@Context

This solution sets valueWithName after mapping of Child object. Iteration through those objects is done only once, but it requires passing a ParentNameContext object to the mapping method.

@Mapper
public interface MyMapper {
  ParentDto map(Parent parent, @Context ParentNameContext parentNameContext);
}
public class ParentNameContext {

  private String parentName;

  @AfterMapping
  public void afterChildMapping(@MappingTarget ChildDto childDto, Child child) {
    childDto.setValueWithName(child.getValue() + parentName);
  }
}

@AfterMapping

This solution doesn't require any additional objects to be passed to the mapping method, but it iterates twice through Child objects because valueWithName is set after Parent object mapping.

@Mapper
public interface MyMapper {
  ParentDto map(Parent parent);

  @AfterMapping
  default void afterMapping(@MappingTarget ParentDto parentDto, Parent parent) {
    List<Child> children = parent.getChildren();
    List<ChildDto> dtoChildren = parentDto.getChildren();
    for (int i = 0; i < children.size(); i++)
      dtoChildren.get(i).setValueWithName(children.get(i).getValue() + parent.getName());
  }
}

To suppress the "Unmapped target property" warning check Anatoly's answer.

Toni
  • 3,296
  • 2
  • 13
  • 34