Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 202
AlexB Avatar asked May 16 '16 12:05

AlexB


People also ask

How do you map a list of objects using MapStruct?

In general, mapping collections with MapStruct works the same way as for simple types. Basically, we have to create a simple interface or abstract class, and declare the mapping methods. Based on our declarations, MapStruct will generate the mapping code automatically.

Is it good to use MapStruct?

All benchmarks have shown that MapStruct and JMapper are both good choices depending on the scenario.

What is componentModel in MapStruct?

Enclosing class: MappingConstants public static final class MappingConstants.ComponentModel extends Object. Specifies the component model constants to which the generated mapper should adhere. It can be used with the annotation Mapper.componentModel() or MapperConfig.componentModel()

What is MappingTarget in MapStruct?

Annotation Type MappingTargetDeclares a parameter of a mapping method to be the target of the mapping. Not more than one parameter can be declared as MappingTarget . NOTE: The parameter passed as a mapping target must not be null .


1 Answers

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.

like image 184
AlexB Avatar answered Sep 28 '22 00:09

AlexB