MapStruct: filter a list before mapping

jengelhart

I've seen this question asked elsewhere but not quite in the same context and without an answer that works for our use case.

Suppose I have a list field in a source object:

List<MySourceElement> mySourceList;

and a corresponding target field:

List<MyTargetElement> myTargetList;

I simply want to be able to filter the elements in the source mySourceList by some attribute of MySourceElement before a standard mapper for mySourceList is executed to map to myTargetList.

Suppose MySourceElement has a boolean attribute isValid and our filter predicate is simply (isValid == true), and that MyTargetElement does not have a corresponding boolean.

I have tried a number of approaches including @DecoratedWith and qualifiedBy:

  1. @DecoratedWith became way too complicated/convoluted with Guice injection for such a simple use case, and this feature is also listed in the MapStruct documentation as experimental with jsr330.

  2. qualifiedBy wasn't working (i.e., I couldn't get mapstruct to apply the qualifiedBy method in the implementation).

The qualifiedBy method was something like:

@FilterForValid    
public List<MySourceElement> filterForValid(List<MySourceElement> mySourceElement) {
    ... implementation ...
}

And my mapper declaration was like:

@Mapping(source = "mySourceList", target = "myTargetList", qualifiedBy = FilterForValid.class)
Target sourceToTarget(Source source);

I wanted an implementation for qualifiedBy like:

target.withMyTargetList( 
    mySourceListToMyTargetList(filterUtil.filterForValid(source.getMySourceList)));    

In lieu of getting qualifiedBy to work, I would be happy figuring out how to use @BeforeMapping for this, but it wasn't clear to me how I would do this from the documentation, especially since for all intents and purposes the source object should be considered immutable.

Any guidance on the simplest, preferred way to do collection filtering in combination with invoking a mapper in this way would be appreciated.

Filip

There is a requested feature mapstruct/mapstruct#1610 that would allow to use an out of the box support for something like this here. Having said this an approach to solve this would be to use the @Context annotation. Your mapper can then look like:

@Mapper
public interface MyMapper {


    Target map(Source source, @Context Predicate<MySourceElement> predicate);

    default List<MySourceElement> mapAndFilter(List<MySourceElement> list, @Context Predicate<MySourceElement> predicate) {
        List<MySourceElement> newList = new ArrayList<>();
        for(MySourceElement el : list) {
            if (predicate.test(el)) {
                newList.add(map(el));
            }
        }

        return newList;
    }

    MySourceElement map(MySourceElement el);
}

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related