Alexander Ziubin
Alexander Ziubin

Reputation: 43

Mapping nested raw List with MapStruct

I am trying to map my legacy API object (I can not change it), which has nested raw List type property. The elements of the list are not compatible with elements of DTO list and should be mapped explicitly as nested. Unfortunately it looks like MapStruct considers raw List as compatible with all typed lists and ignores any mapping, which I try to specify and the generated code does not work correctly producing error later, somewhere in serializer.

My API object has nested raw type list:

@Getter
public class ApiObject {
    protected Long uid;
    protected String name;
    List values; // Raw type list. Elements not compatible with DTO Value type.
}

My DTO object has generic typed list of DTO Value type elements:

@Setter
public class DtoObject {
    protected Long uid;
    protected String name;
    List <Value> values; // Should be mapped with ApiObject.values
}

In the mapper abstract class I am trying to specify custom mapper, which understands how to map API values to DTO values:

@Mapping(target = "values", source = "values", qualifiedByName = "myCustomValueMapper")
public abstract DtoObject map(final ApiObject apiObject);

... but MapStruct still ignores myCustomValueMapper and the produced mapper impl object looks like below (note that incompatible API value objects assigned to DTO value objects):

@Override
public DtoObject map(APiObject apiObject) {
    if ( apiObject == null ) {
        return null;
    }

    DtoObject dtoObject = new DtoObject();

    List list = apiObject.getValues();
    if ( list != null ) {
        dtoObject.setValues( new ArrayList<Value>( list ) );  // This code produces error later, in serializer.
    }
    else {
        dtoObject.setValues( null );
    }
    dtoObject.setUid( apiObject.getUid() );
    dtoObject.setName( apiObject.getName() );

    return dtoObject;
}

Is there any possibility in MapStruct to specify mapping for elements of nested raw List, or may be any workaround to achieve compatible mapping?

Upvotes: 1

Views: 2212

Answers (1)

Bart Robeyns
Bart Robeyns

Reputation: 581

You can use a default method to explicitly manage the conversion of the items in the list:

@Mapper
public interface ApiMapper {

    @Mapping(target = "values", source = "values")
    DtoObject map(final ApiObject apiObject);

    default List<Value> mapToValueList(List list) {
        List<Value> values = new ArrayList<>();
        for (Object o: list) {
            Value value = new Value();
            // here goes code to convert o -> Value
            // ...
            values.add(value);
        }
        return values;
    }
}

The generated code looks like this:

public class ApiMapperImpl implements ApiMapper {

    @Override
    public DtoObject map(ApiObject apiObject) {
        if ( apiObject == null ) {
            return null;
        }

        DtoObject dtoObject = new DtoObject();
        if ( dtoObject.getValues() != null ) {
            List<Value> list = mapToValueList( apiObject.getValues() );
            if ( list != null ) {
                dtoObject.getValues().addAll( list );
            }
        }
        return dtoObject;
    }
}

List<Value> list = mapToValueList( apiObject.getValues() ); will call the default method on the interface (note that ApiMapperImpl implements the ApiMapper interface).

Upvotes: 3

Related Questions