java - ModelMapper:使用带有自定义映射的 TypeMap 时,如何使转换器在映射器级别注册

标签 java modelmapper

我使用 ModelMapper 将模型转换为 DTO。我有一堆空值的默认转换器,它们已在映射器级别注册,如下所示:

        modelMapper.addConverter(new Converter<String, String>() {
            @Override
            public String convert(MappingContext<String, String> context) {
                if (context.getSource() == null) {
                    return "global null converter was here";
                }
                return context.getSource();
            }
        });

当属性名称在转换的两端相同时,这对于简单映射可以很好地工作。 转换器用于按预期处理空值。

现在,如果我需要通过在类型映射上使用 .map(getter, setter) 对不同的属性名称进行更复杂的转换,则不再调用全局转换器。 我不希望在配置类型映射时丢弃全局转换器。

我该如何解决?

下面是使用 ModelMapper 2.3.8(今天的最新版本)的示例代码(为代码简洁而使用 lombok):

@Data @AllArgsConstructor @NoArgsConstructor class A { String a; String b;}

@Data @AllArgsConstructor @NoArgsConstructor class B { String a; String b; }

@Data @AllArgsConstructor @NoArgsConstructor class C { String x; String y;}

public class MapperTestCase {
    

    public static void main(String[] args) throws IOException {

        A a = new A("aaa", "bbb");
        
        ModelMapper modelMapper = new ModelMapper();
        final TypeMap<A, B> AtoBTypeMap = modelMapper.createTypeMap(A.class, B.class);
        B b = AtoBTypeMap.map(a);
        System.out.println("conversion with no converter A -> B: " + a + " -> " + b);

        a = new A(null, null);
        b = AtoBTypeMap.map(a);
        System.out.println("conversion with no converter A -> B: " + a + " -> " + b);

        // Add a global/fallback converter that should convert all null String values.
        modelMapper.addConverter(new Converter<String, String>() {
            @Override
            public String convert(MappingContext<String, String> context) {
                if (context.getSource() == null) {
                    return "global null converter was here";
                }
                return context.getSource();
            }
        });            

        final TypeMap<B, A> BtoATypeMap = modelMapper.typeMap(B.class, A.class);
        a = BtoATypeMap.map(b);
        System.out.println("conversion with global converter B -> A: " + b + " -> " + a);
        
        // add a local converter for the B to C type mape only
        BtoATypeMap.addMappings(mapper -> mapper.using(ctx -> {
            if (ctx.getSource() == null) {
                return "local converter was here";
            } else return ctx.getSource();
        }).map(B::getA, (w, x) -> w.setA(String.valueOf(x))));

        // in this conversion both converter (global and local) should be used
        a = BtoATypeMap.map(b);
        System.out.println("conversion with global and local converter B -> A: " + b + " -> " + a);

        // a new typeMap that will transform a B into a C, mapping B::a to C::x and B::b to C::y
        final TypeMap<B, C> BtoCTypeMap = modelMapper.typeMap(B.class, C.class);
        
        // a local converter for this type map
        BtoCTypeMap.addMappings(mapper -> mapper.using(ctx -> {
            if (ctx.getSource() == null) {
                return "local converter was here";
            } else return ctx.getSource();
        }).map(B::getA, (w, x) -> w.setX(String.valueOf(x))));
        
        BtoCTypeMap.addMapping(B::getB, C::setY);
        // first a conversion with a B instance without null values, works as expected
        b = new B("some", "data");
        C c = BtoCTypeMap.map(b);
        System.out.println("conversion with global and local converter B -> C: " + b + " -> " + c);

        // now a conversion with a B instance wirth null values, the local converer will be used, but not the global one defined at the mapper level. Why ?
        b = new B();
        c = BtoCTypeMap.map(b);
        System.out.println("conversion with global and local converter B -> C: " + b + " -> " + c);
    }
}

输出是:

conversion with no converter A -> B: A(a=aaa, b=bbb) -> B(a=aaa, b=bbb)
conversion with no converter A -> B: A(a=null, b=null) -> B(a=null, b=null)
conversion with global converter B -> A: B(a=null, b=null) -> A(a=global null converter was here, b=global null converter was here)
conversion with global and local converter B -> A: B(a=null, b=null) -> A(a=local converter was here, b=global null converter was here)
conversion with global and local converter B -> C: B(a=some, b=data) -> C(x=some, y=data)
conversion with global and local converter B -> C: B(a=null, b=null) -> C(x=local converter was here, y=null)

最后一行的预期输出是 C(x=local converter was here, y=global null converter was here)

最佳答案

如果你想创建通用的 propertyConverter,你可以尝试这样的东西

        Converter<String, String> stringPropertyConverter = new Converter<String, String>() {
        @Override
        public String convert(MappingContext<String, String> context) {
            if (context.getSource() == null) {
                return "global null converter was here";
            }
            return context.getSource();
        }
    };

    ModelMapper modelMapper = new ModelMapper() {
        @Override
        public <S, D> TypeMap<S, D> typeMap(Class<S> sourceType, Class<D> destinationType) {
            TypeMap<S, D> typeMap = super.typeMap(sourceType, destinationType);
            typeMap.setPropertyConverter(stringPropertyConverter);
            return typeMap;
        }

    }; 

在映射过程中使用的顺序转换器通常会出现问题。 首先 modelMapper 为您的类定义转换器,下一步它会为类的字段搜索合适的转换器。 在第一种情况下,您的转换器已按顺序放置

"TypeMap[String -> String]"
"TypeMap[B -> A]"
"TypeMap[A -> B]"

第二种情况

"TypeMap[B -> C]"
"TypeMap[String -> String]"
"TypeMap[B -> A]"
"TypeMap[A -> B]"

转换器 B 到 C 适合您类(class)中任何领域的转换器。

关于java - ModelMapper:使用带有自定义映射的 TypeMap 时,如何使转换器在映射器级别注册,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63593503/

相关文章:

java - 我正在尝试将命令输出写入文件。它不是可读的格式

java - 如何在 ORMLITE android On Upgrade 方法中添加主键?

java - 模拟 ModelMapper 中的 map() 方法返回 null 值

java - 如何在 java 中使用 DMA 或 RDMA?

java - 如何为 JSONSchema 自定义错误消息?

java - 如何将 DTO 映射到现有的 JPA 实体?

java - 如果是 @Transactional,则删除方法不会影响数据库

java - 模型映射器计算

java - 如何在java中实现嵌套数组列表?