MapStruct - 将通用列表映射到包含不同类型的非通用列表

MapStruct - Map Generic List to Non-Generic List containing different types

我正在尝试使用 MapStruct 在 2 个内部模型之间进行转换。这些模型是根据相同的规范生成的,代表了一个不断添加的大型树状结构。使用 MapStruct 的目标是拥有一种高效、低(非生成)代码的方式来执行此转换,并与规范的添加保持同步。例如,我的模型看起来像:

package com.mycompany.models.speca;

public class ModelSpecA {
    private String name;
    private int biggestNumberFound;
    private com.mycompany.models.speca.InternalModel internalModel;
    private List<com.mycompany.models.speca.InternalModel> internalModelList;
}
package com.mycompany.models.specb;

public class ModelSpecB {
    private String name;
    private int biggestNumberFound;
    private com.mycompany.models.specb.InternalModel internalModel;
    private List internalModelList;
}

具有所有预期的 getter 和 setter 以及无参数构造函数。 MapStruct 能够非常轻松地生成映射代码,代码如下所示:

interface ModelSpecMapper {
    ModelSpecB map(ModelSpecA source);
}

从单元测试和检查生成的代码来看,映射是准确和完整的,除了一个方面:每个 class 中 internalModelList 成员的映射。生成的代码如下所示:

...
if (sourceInternalModelList != null) {
    specBTarget.setInternalModelList( specASource.getInternalModelList() );
}
...

即它是从泛型 List 映射到非泛型 List 而无需进行模型转换。这在单元测试的编译时和运行时通过,但当我们希望能够转换为模型的 SpecB 版本时,会在以后的代码中导致错误。

到目前为止,我已经研究了是否可以在不使用昂贵的反射操作的情况下强制将源中的参数化类型映射到其对应的类型,这将消除使用 MapStruct 作为解决方案的好处。这是我第一次使用 MapStruct,因此可能有一个我根本不知道的明显解决方案。添加显式映射是不可行的,因为我需要它与未来添加到模型中的内容(包括新列表)向前兼容。

TLDR;如何使用 MapStruct 将通用列表的内容转换为非通用列表?例如。 List<com.mycompany.a.ComplexModel> --> List 其成员类型为 com.mycompany.b.ComplexModel.

根据@chrylis -cautiouslyoptimistic- 的建议,我设法通过使用 Jackson 直接执行从类型到类型的映射成功完成了映射,但这与 MapStruct 问题无关。通过将默认映射添加到我的 MapStruct 映射器,我能够实现将通用列表映射到 non-generic 列表的既定目标:

     /**
      * Map a generic List which contains object of any type to a non-generic List which will contain objects
      * of the resulting mapping.
      * E.g. It maps a generic list of T to a non-generic list of contents mapped from T.
      * @param source Source generic List to map from.
      * @return A non-generic List whose contents are the contents of <i>source</i> with mapping implementation applied.
      * @param <T>
      */
     default <T> List mapGenericToNonGeneric(List<T> source) {
         if (source == null) {
             return null;
         }
         if (source.isEmpty()) {
             return new LinkedList();
         }
         // Handle the most common known cases as an optimization without expensive reflection.
         final Class<?> objectClass = source.get(0).getClass();
         if (ClassUtils.isPrimitiveOrWrapper(objectClass)) {
             return new LinkedList(source);
         }
 
         if (String.class.equals(objectClass)) {
             return new LinkedList(source);
         }
    
         try {
             Method mapperMethod = Stream.of(this.getClass().getDeclaredMethods())
                     .map(method -> {
                         Parameter[] params = method.getParameters();
                         // If this method is a mapper that takes the type of our list contents
                         if (params.length == 1 && params[0].getParameterizedType().equals(objectClass)) {
                             return method;
                         }
                         return null;
                     })
                     .filter(Objects::nonNull)
                     .findFirst()
                     .orElse(null);
             if (mapperMethod != null) {
                 final List result = new LinkedList();
                 for (T sourceObject : source) {
                     result.add(mapperMethod.invoke(this, sourceObject));
                 }
                 log.info("Executed slow generic list conversion for type {}", objectClass.getName());
                 return result;
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
         return null;
     }

从检查生成的代码和向每个集合内容的类型添加断言,这通过了我的 property-based 测试套件。它仍然使用反射来确定参数化类型,但能够处理对模型的任意添加,并且大大优于以前的 Jackson 解决方案。