ModelMapper:将属性从根传输到列表的每个元素
ModelMapper: transferring attribute from root to each elements of a list
型号
我们使用 org.modelmapper
进行 bean 映射。这是数据模型(为简洁起见,使用 public
):
class Root {
public String foo;
public List<Element> elements;
}
class Element {
public String foo; // <- this is the field that should be replicated from RootDTO
public String bar;
}
class RootDTO {
public String foo;
public List<ElementDTO> elements;
}
class ElementDTO {
public String bar; // notice how there is no `foo` attribute in the DTO
}
预期映射(示例)
输入:
RootDTO
|-- foo = "foo"
|-- elements
|--ElementDTO(bar="bar")
|--ElementDTO(bar="something")
|--ElementDTO(bar="else")
输出:
Root
|-- foo = "foo"
|-- elements
|--Element(foo="foo", bar="bar")
|--Element(foo="foo", bar="something")
|--Element(foo="foo", bar="else")
问题
如您所见,如果 Element.foo
字段不应该从 RootDTO.foo
.[=32= 获取值,ModelMapper
会很容易地处理这种情况]
如何配置 ModelMapper
来实现我的目标?
我想要解决方案:
- 如果要在模型中引入新的匹配属性,则不需要开发人员更改映射代码。
- 通过对
ModelMapper
的单个方法调用来完成。
当前配置
myModelMapper.typeMap(ElementDTO.class, Element.class).implicitMappings();
myModelMapper.typeMap(RootDTO.class, Root.class).implicitMappings();
使用该配置时的结果
Root mappedRoot = myModelMapper.map(rootDto, Root.class);
assertEquals(rootDto.foo, mappedRoot.foo);
assertEquals(rootDto.elements.size(), mappedRoot.elements.size());
// let's assume we have 1 Element
assertEquals(rootDto.elements.get(0).bar, mappedRoot.elements.get(0).bar);
assertEquals(rootDto.foo, mappedRoot.elements.get(0).foo); // FAILS! But this is what I want
// indeed, the value of `mappedRoot.elements.get(0).foo` is `null` because it was not mapped
已探索的途径
想法 1
在我看来,如果我可以设置一个 order 我可以简单地这样配置它:
Converter<RootDTO, Root> replicateFooValue = new Converter<>() {
@Override
public Root convert(MappingContext<RootDTO, Root> context) {
final String valueToReplicate = context.getSource().foo;
for (Element elem : context.getDestination().elements) {
elem.foo = valueToReplicate;
}
return context.getDestination();
}
};
myModelMapper.typeMap(ElementDTO.class, Element.class).implicitMappings();
myModelMapper.typeMap(RootDTO.class, Root.class).implicitMappings()
.thenUseConverter(replicateFooValue);
...但我认为这是不可能的。
想法 2
如果我可以在 Converter<>
本身中使用 ModelMapper
的映射功能,那么我可以在尝试简单地设置我想要的值之前使用 implicitMappings()
,但是再一次:我不认为这是可能的。
想法 3
确保它有效的一种方法是:
Converter<RootDTO, Root> myCustomConverter = new Converter<>() {
@Override
public Root convert(MappingContext<RootDTO, Root> context) {
final Root mappedRoot = new Root();
// map each field individually, by hand
return mappedRoot;
}
};
myModelMapper.addConverter(myCustomConverter);
...但是如果我要在我的数据模型中添加新字段,它需要维护。
想法 4
添加一个 Converter<RootDTO, Element>
,它只会填充 foo
值。这将导致以下用法:
Root mappedRoot = myModelMapper.map(rootDto, Root.class);
mappedRoot.elements.forEach(e -> myModelMapper.map(rootDto, e));
这并不理想,因为整个预期行为并没有封装在单个调用中:开发人员必须知道(并记住)进行第二次调用以实现所需的映射。
想法 5
创建一个包含 Idea 4
所示逻辑的实用程序 class。
这也是一个坏主意,因为它要求开发人员知道(并记住)为什么他们需要以这种方式进行特定映射,而不是使用 ModelMapper
.
原来是一种引入一些顺序的方法。这是解决方案:
Converter<RootDTO, Root> finishMapping = new Converter<>() {
@Override
public Root convert(MappingContext<RootDTO, Root> context) {
final String srcFoo = context.getSource();
var dest = context.getDestination();
dest.elements.stream().forEach(e -> e.foo = srcFoo);
return dest;
}
};
myModelMapper.typeMap(ElementDTO.class, Element.class).implicitMappings();
myModelMapper.typeMap(RootDTO.class, Root.class).implicitMappings()
.setPostConverter(finishMapping); // this is executed AFTER the implicit mapping
型号
我们使用 org.modelmapper
进行 bean 映射。这是数据模型(为简洁起见,使用 public
):
class Root {
public String foo;
public List<Element> elements;
}
class Element {
public String foo; // <- this is the field that should be replicated from RootDTO
public String bar;
}
class RootDTO {
public String foo;
public List<ElementDTO> elements;
}
class ElementDTO {
public String bar; // notice how there is no `foo` attribute in the DTO
}
预期映射(示例)
输入:
RootDTO
|-- foo = "foo"
|-- elements
|--ElementDTO(bar="bar")
|--ElementDTO(bar="something")
|--ElementDTO(bar="else")
输出:
Root
|-- foo = "foo"
|-- elements
|--Element(foo="foo", bar="bar")
|--Element(foo="foo", bar="something")
|--Element(foo="foo", bar="else")
问题
如您所见,如果 Element.foo
字段不应该从 RootDTO.foo
.[=32= 获取值,ModelMapper
会很容易地处理这种情况]
如何配置 ModelMapper
来实现我的目标?
我想要解决方案:
- 如果要在模型中引入新的匹配属性,则不需要开发人员更改映射代码。
- 通过对
ModelMapper
的单个方法调用来完成。
当前配置
myModelMapper.typeMap(ElementDTO.class, Element.class).implicitMappings();
myModelMapper.typeMap(RootDTO.class, Root.class).implicitMappings();
使用该配置时的结果
Root mappedRoot = myModelMapper.map(rootDto, Root.class);
assertEquals(rootDto.foo, mappedRoot.foo);
assertEquals(rootDto.elements.size(), mappedRoot.elements.size());
// let's assume we have 1 Element
assertEquals(rootDto.elements.get(0).bar, mappedRoot.elements.get(0).bar);
assertEquals(rootDto.foo, mappedRoot.elements.get(0).foo); // FAILS! But this is what I want
// indeed, the value of `mappedRoot.elements.get(0).foo` is `null` because it was not mapped
已探索的途径
想法 1
在我看来,如果我可以设置一个 order 我可以简单地这样配置它:
Converter<RootDTO, Root> replicateFooValue = new Converter<>() {
@Override
public Root convert(MappingContext<RootDTO, Root> context) {
final String valueToReplicate = context.getSource().foo;
for (Element elem : context.getDestination().elements) {
elem.foo = valueToReplicate;
}
return context.getDestination();
}
};
myModelMapper.typeMap(ElementDTO.class, Element.class).implicitMappings();
myModelMapper.typeMap(RootDTO.class, Root.class).implicitMappings()
.thenUseConverter(replicateFooValue);
...但我认为这是不可能的。
想法 2
如果我可以在 Converter<>
本身中使用 ModelMapper
的映射功能,那么我可以在尝试简单地设置我想要的值之前使用 implicitMappings()
,但是再一次:我不认为这是可能的。
想法 3
确保它有效的一种方法是:
Converter<RootDTO, Root> myCustomConverter = new Converter<>() {
@Override
public Root convert(MappingContext<RootDTO, Root> context) {
final Root mappedRoot = new Root();
// map each field individually, by hand
return mappedRoot;
}
};
myModelMapper.addConverter(myCustomConverter);
...但是如果我要在我的数据模型中添加新字段,它需要维护。
想法 4
添加一个 Converter<RootDTO, Element>
,它只会填充 foo
值。这将导致以下用法:
Root mappedRoot = myModelMapper.map(rootDto, Root.class);
mappedRoot.elements.forEach(e -> myModelMapper.map(rootDto, e));
这并不理想,因为整个预期行为并没有封装在单个调用中:开发人员必须知道(并记住)进行第二次调用以实现所需的映射。
想法 5
创建一个包含 Idea 4
所示逻辑的实用程序 class。
这也是一个坏主意,因为它要求开发人员知道(并记住)为什么他们需要以这种方式进行特定映射,而不是使用 ModelMapper
.
原来是一种引入一些顺序的方法。这是解决方案:
Converter<RootDTO, Root> finishMapping = new Converter<>() {
@Override
public Root convert(MappingContext<RootDTO, Root> context) {
final String srcFoo = context.getSource();
var dest = context.getDestination();
dest.elements.stream().forEach(e -> e.foo = srcFoo);
return dest;
}
};
myModelMapper.typeMap(ElementDTO.class, Element.class).implicitMappings();
myModelMapper.typeMap(RootDTO.class, Root.class).implicitMappings()
.setPostConverter(finishMapping); // this is executed AFTER the implicit mapping