mapstructvavr

Use Vavr list in Mapstruct - java: No implementation type is registered for return type io.vavr.collection.List


I am trying to use Mapstruct on an object which has Vavr list.

But I get the error java: No implementation type is registered for return type io.vavr.collection.List but works fine on java.util.List

I see another question few years back about "Page" datatype Mapstruct return type which has a similar error but I don't see any updates.

Is there no fix? I can change the vavr list to Java list and vice versa when calling a mapstruct mapper on an object, but if the object has child objects which have vavr list then I am not sure what can be done, because inside mapstruct mapper file I cannot do anything.

I am using the latest mapstruct version:

      <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.4.2.Final</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.4.2.Final</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-mapstruct-binding</artifactId>
            <version>0.2.0</version>
        </dependency>

ParentObject:

import io.vavr.collection.List;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class ParentObject {
    private String parentField;

    private List<ParentInnerObject> parentInnerObjectList;

ParentInnerObject:

@Data
@Builder
public class ParentInnerObject {
    private String parentInnerField;

ChildObject:

import io.vavr.collection.List;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class ChildObject {
    private String childField;

    private List<ChildInnerObject> childInnerObjectList;

ChildInnerObject:

@Data
@Builder
public class ChildInnerObject {
    private String childInnerField;

Sample mapper to map the ParentObject to ChildObject:

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.WARN)
public abstract class TestMapper {

    @Mapping(source = "parentField", target = "childField")
    @Mapping(source = "parentInnerObjectList", target = "childInnerObjectList")
    public abstract ChildObject toChildObject(final ParentObject parentObject);

    public abstract List<ChildInnerObject> toChildObjects(
            final List<ParentInnerObject> parentInnerObjectList);

    @Mapping(source = "parentInnerField", target = "childInnerField")
    public abstract ChildInnerObject toChildInnerObject(final ParentInnerObject parentInnerObject);

Test class to test the mapper code:

import io.vavr.collection.List;

@ExtendWith(MockitoExtension.class)
class TestMapperTest {
  //https://stackoverflow.com/questions/34067956/how-to-use-injectmocks-along-with-autowired-annotation-in-junit
  @Autowired
  @InjectMocks
  private TestMapperImpl testMapper;

  @Test
  @DisplayName("Check if the value is returned correctly")
  void testMapperCode(){
    ParentObject pa = ParentObject.builder()
            .parentField("parent field")
            .parentInnerObjectList(List.of(ParentInnerObject.builder()
                    .parentInnerField("parent inner field")
                    .build()))
            .build();
    /*ParentObject pa = ParentObject.builder()
            .parentField("parent field")
            .parentInnerObjectList(Collections.singletonList(ParentInnerObject.builder()
                    .parentInnerField("parent inner field")
                    .build()))
            .build();*/
    ChildObject childObject = testMapper.toChildObject(pa);
    System.out.println(childObject);
  }

}

Edit:- use custom mapper for now until vavr list is supported in mapstruct

import io.vavr.collection.Iterator;
import io.vavr.collection.List;

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.WARN)
public abstract class TestMapper {
    @Mapping(source = "parentField", target = "childField")
    //@Mapping(source = "parentInnerObjectList", target = "childInnerObjectList") //This does not work as Mapstruct does not support Vavr list yet
    @Mapping(target = "childInnerObjectList", expression = "java(vavrListSetter(parentObject.parentInnerObjectList))") //custom mapper
    public abstract ChildObject toChildObject(final ParentObject parentObject);

    @Mapping(source = "parentInnerField", target = "childInnerField")
    public abstract ChildInnerObject toChildInnerObject(final ParentInnerObject parentInnerObject);

    public List<ChildInnerObject> vavrListSetter(final List<ParentInnerObject> parentInnerObjectList) {
        if (parentInnerObjectList == null) {
            return null;
        }
        return Iterator.ofAll(parentInnerObjectList)
                .map(this::toChildInnerObject)
                .collect(List.collector());
    }

Solution

  • The Spring Data Page data type is different compared to the Vavr List. The Page data type needs to be treated as a bean and not an iterable and this will be fixed in the upcoming 1.5 release.

    As for the Vavr List MapStruct doesn't support using that right now. You can raise a feature request so we can discuss potential solutions.

    Currently the only way for this is to write custom mapping methods for mapping between those lists.