MapStruct Map Object to List - java

I am trying to use Mapstruct to map source object to a target list. What should be a clean mapstruct way of doing this?
Below are my DTO's.
Source DTO
#Data
class Source
{
String a;
String b;
String C;
}
Target DTO
#Data
class Target
{
String name;
List<Child> customList;
}
#Data
class Child
{
String attr1;
boolean attr2;
}
I am facing issues with Mapper Class. Trying to achieve something like below.
public interface CustomMapper
{
#Mapper(target="customList" expression="java(new Child(a,false))"
#Mapper(target="customList" expression="java(new Child(b,true))"
#Mapper(target="customList" expression="java(new Child(c,false))"
Target sourceToTarget(Source source);
}
I don't want to use qualifiedBy function like below to achieve this, as all conversion needs to be coded for each element.
List<Child> toList(Source source)
{
List<Child> customList = new ArrayList<Child>();
customList.add(new Child(source.getA(),false));
customList.add(new Child(source.getB(),true));
customList.add(new Child(source.getC(),false));
return customList;
}

I have used an expression to solve this problem. The expression is to perform the mapping (for objects, straightforward for Strings), and then convert it to a list.
#Mapping(target = "names", expression = "java(Arrays.asList(map(source.getName())))")
TargetObject map(SourceObject source);
TargetName map(SourceName source)
You need to import "Arrays" class in the #Mapper definition as below.
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring", imports = {Arrays.class})

I had similar use case where i want to convert and Single Object to List of objects.
As these are very custom requirements, will be hard for mapstruct to provide some API for use cases like this.
I ended up implementing using default method like this
#Mapper(componentModel = "spring")
interface MyMapper {
default List<SomeObject> from(SourceObject sourceObject) {
//Add Mappig logic here
//return the list
}
}
//If you want to use some mapper for mapping
#Mapper(componentModel = "spring")
public abstract class SomeArrayMapper {
#Autowired
SomeMapper mapper;
public SomeUser[] from(SingleObject singleObject) {
SomeUsers[] Users = new SomeUser[1];
Users[0] = mapper.toUser(singleObject);;
return Users ;
}
}
In some cases Decorators can also be useful take a look at here

There is no clean way of doing this at the moment in MapStruct. MapStruct is considerring bean tot map mapping. See here: https://github.com/mapstruct/mapstruct/pull/1744 which might come in helpful once implemented.
However, if you really have a lot of properties and this is a recurring problem, and you dislike reflection - like I do - you might want to give code generation an attempt and DIY. I posted an example some while ago for generating a mapper repository here: https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-mapper-repo but, it's a bit of an steep learning curve.. sorry

Related

MapStruct inheritance and calling "this"

I have a denormalized database and a domain model that includes multiple classes. for example
class BMW {
EngineConfiguration engineConfiguration;
ModelMetadata modelMetadata;
BodyConfiguration bodyConfiguration;
}
class CarsEntity{
String modelName;
Integer year...
//etc. all fields, in same class
}
I would like to write MapStruct mappers for EngineConfiguration, ModelMetadata and BodyConfiguration and then when uniting them all in one mapper. Example
#Mapper(componentModel = "spring")
class EngineConfigurationMapper {
#Mapping(... specific fields mapping goes here)
EngineConfiguration mapToDomain(CarsEntity carsEntity);
}
#Mapper(componentModel = "spring", uses = {EngineConfigurationMapper.class , ...})
class BMWMapper {
BMW mapToDomain(CarsEntity carsEntity);
}
The problem is that my mappers are not working by default, BMWMapper does not have the calls to the others mappers and I've tried expression calling this like expression="(engineConfigurationMapper.mapToDomain(this))" target = "engineConfiguration) but the mappers are not included in the implementation.
How can I achieve this?
P.S.: MapperConfig is of no use as well
I guess when you say this you mean the source entity, i.e. carsEntity.
In Mapping#source you can use the source parameter name, which means that you can do the following:
#Mapper(componentModel = "spring", uses = {EngineConfigurationMapper.class , ...})
class BMWMapper {
#Mapping( target = "engineConfiguration", source = "carsEntity")
#Mapping( target = "modelMetadata", source = "carsEntity")
#Mapping( target = "bodyConfiguration", source = "carsEntity")
BMW mapToDomain(CarsEntity carsEntity);
}

How to implement a partial custom mapping with MapStruct?

I would like MapStruct to map every property of my Object, except for one particular one for which I would like to provide a custom mapping.
So far, I implemented the whole mapper myself, but every time I add a new property to my Entity, I forget to update the mapper.
#Mapper(componentModel = "cdi")
public interface MyMapper {
MyMapper INSTANCE = Mappers.getMapper(MyMapper.class);
default MyDto toDTO(MyEntity myEntity){
MyDto dto = new MyDto();
dto.field1 = myEntity.field1;
// [...]
dto.fieldN = myEntity.fieldN;
// Custom mapping here resulting in a Map<> map
dto.fieldRequiringCustomMapping = map;
}
}
Is there a way to outsource the mapping for my field fieldRequiringCustomMapping and tell MapStruct to map all the other ones like usual? 🤔
MapStruct has a way to use custom mapping between some fields. So in your case you can do someting like:
#Mapper(componentModel = "cdi")
public interface MyMapper {
#Mapping(target = "fieldRequiringCustomMapping", qualifiedByName = "customFieldMapping")
MyDto toDTO(MyEntity myEntity);
// The #(org.mapstruct.)Named is only needed if the Return and Target type are not unique
#Named("customFieldMapping")
default FieldRequiringCustomMapping customMapping(SourceForFieldRequiringCustomMapping source) {
// Custom mapping here resulting in a Map<> map
return map
}
}
I do not know from what your fieldRequiringCustomMapping needs to be mapped, the example assumes that you have such a field in MyEntity as well, if that is not the case you'll need to add source to the #Mapping.
Side Note: When using a non default componentModel in your case cdi it is not recommended to use the Mappers factory. It will not perform the injection of other mappers in case you use them in a mapper.

How to call MapStruct method from interface in java stream

I recently started using the MapStruct mapping tool in a project. In the past, for mapping DTO -> Entity and vice versa I used custom mapper like:
public static CustomerDto toDto(Customer customer) {
return isNull(customer)
? null
: CustomerDto.builder()
.id(customer.getId())
.name(customer.getName())
.surname(customer.getSurname())
.phoneNumber(customer.getPhoneNumber())
.email(customer.getEmail())
.customerStatus(customer.getCustomerStatus())
.username(customer.getUsername())
.NIP(customer.getNIP())
.build();
}
In case when I was trying to get one single Optional object after all I was able to map my entity to dto in the following way:
public Optional<CustomerDto> findOneById(final long id) {
return customerRepository.findById(id).map(CustomerMapper::toDto);
}
Currently, as I mentioned before I am using mapStruct and the problem is that my mapper it's, not class, it's the interface like:
#Mapper
public interface CommentMapper {
#Mappings({
#Mapping(target = "content", source = "entity.content"),
#Mapping(target = "user", source = "entity.user")
})
CommentDto commentToCommentDto(Comment entity);
#Mappings({
#Mapping(target = "content", source = "dto.content"),
#Mapping(target = "user", source = "dto.user")
})
Comment commentDtoToComment(CommentDto dto);
}
I want to know if it possible to use somehow this interface method in stream gentle to map my value without wrapping values like:
public Optional<CommentDto> findCommentById(final long id) {
Optional<Comment> commentById = commentRepository.findById(id);
return Optional.ofNullable(commentMapper.commentToCommentDto(commentById.get()));
}
Thanks for any help.
Access the mapper like:
private static final YourMapper MAPPER = Mappers.getMapper(YourMapper.class);
final Optional<YourEntity> optEntity = entityRepo.findById(id);
return optEntity.map(MAPPER::toDto).orElse(null);
Basically we do a similar thing with enumerations
#Mapping(target = "type", expression = "java(EnumerationType.valueOf(entity.getType()))")
you can define java expressions in your #Mapping annotation
#Mapping(target = "comment", expression = "java(commentMapper.commentToCommentDto(commentRepository.findById(entity.commentId).orElse(null)))"
Otherwise you should be able to make use of a
class CommentMapper { ... }
which you automatically can refer with
#Mapper(uses = {CommentMapper.class})
your implementation will detect the commentEntity and Dto and will automatically use the CommentMapper.
A MapStruct mapper is workling like: Shit in Shit out, so remember your entity needs the commentEntity so the dto can has the commentDto.
EDIT
2nd solution could be using:
#BeforeMapping
default void beforeMappingToDTO(Entity source, #MappingTarget Dto target) {
target.setComment(commentMapper.commentToCommentDto(commentRepository.findById(entity.commentId).orElse(null)));
}
#Spektakulatius answer solved a problem.
To reach a goal I made a few steps:
First of all, I created an object of my mapper to use it in a java stream:
private CommentMapper commentMapper = Mappers.getMapper(CommentMapper.class);
In the last step I used my object commentMapper like:
public Optional<CommentDto> findCommentById(final long id) {
return commentRepository.findById(id).map(CommentMapper.MAPPER::commentToCommentDto);
}
After all that completely gave me a possibility to use my custom MapStruct mapper in stream.

Mapstruct: map list properties by use of a qualified IterableMapping

At first, let me apologize if this question was already posed. I could not find any reference in StackOverflow.
I am trying to use a qualification in my mapping between beans via MapStruct, so as to convert between lists via a qualified mapping. Alas, without success.
Let us suppose we have the following classes (simplified as much as I can, and I will omit obvious getters/setters):
public class A {
private String propertyA;
}
public class B {
private String propertyB;
private A instanceA;
}
public class C {
private List<B> instancesB;
}
public class A1 {
private String propertyA;
}
public class B1 {
private String propertyB;
private A1 instanceA1;
}
public class C1 {
private List<B1> instancesB1;
}
Let us suppose to have the following qualifier:
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD })
public #interface Full{}
Let us also suppose to have the following mappings:
#Mapper
public interface MapperA {
A1 toA1(A a);
A toA(A1 a1);
}
#Mapper
public interface MapperB {
B1 toB1(B b);
#Mapping(source="propertyA", target="propertyA1")
#Full
B1 toB1Full(B b);
#IterableMapping(qualifiedBy=Full.class)
#Full
List<B1> toB1s(List<B> bs);
toB(B1 b);
#Mapping(source="propertyA1", target="propertyA")
#Full
toBFull(B1 b);
#IterableMapping(qualifiedBy=Full.class)
#Full
List<B> toBs(List<B1> bs);
}
#Mapper
public interface MapperC {
<HERE IS THE PROBLEM>
}
How can I write the Mapper C so as to use the full mapping for the iterated instances of B?
No matter how I try to write the annotations for the mapping, such as
#Mapping(source = "instancesB1", target="instancesB", qualifiedBy=Full.class)
I always find myself with an incomplete mapped entity: the B1 instance has a correctly mapped propertyB field, but no instanceA1.
I can of course just write MapperC as an abstract class, implement the method, call manually the mapper and just be happy with it, but I'm questioning whether it is possible to just annotate somehow the method in MapperC and have Mapstruct automagically use the correct mapping method for me (since this is an oversimplified case, but I may have tens of such lists to convert).
Thanks for the attention.
Not sure if you have tried that, but looking from the example it seems like the Mapper#uses is missing.
In your case it should look something like (omitted the mapping methods):
#Mapper
public interface MapperA {
}
#Mapper(uses = MapperA.class)
public interface MapperB {
}
#Mapper(uses = MapperB.class)
public interface MapperC {
}
When you use Mapper#uses then MapStruct will look for the qualified methods in the classes defined in uses. However, if you don't have that then there is nothing MapStruct could do and would generate some default mapping.

Mapstruct: Extract attribute to an object

I am using Mapstruct (1.2.0.Final) to map dto objects where I'd like to extract an attribute of an object to its own object instance.
Here is a simplified example:
#Data
public class ExternalResult {
#JsonProperty("items")
List<Item> items;
}
#Data
public class MyItem {
String name;
}
Now I'd like to extract the items from ExternalResult and have them mapped to a list of MyItems. Here is my Mapper and I don't know what to use in as target:
#Mapper(componentModel = "spring")
public interface GraphhopperMapper {
#Mappings({
#Mapping(target = "??", source="items")
})
List<MyItem> mapItems(ExternalResult externalResult);
}
How can this be achieved? Or is there a more convenient way to get rid of the (useless) object with only one attribute?
Thanks in advance.
It's one of the cases where I'd recommend to just implement the method yourself (e.g. by making the mapper an abstract class) instead of trying to let MapStruct do it for you:
List<MyItem> mapItems(ExternalResult externalResult) {
return externalResult.getItems()
.stream()
.map(i -> new MyItem(i.getName())
.collect(Collectors.toList());
}
MapStruct idea is to help you automate the 90% of trivial mappings but let you hand-write the remaining more special cases like this.

Categories

Resources