How to implement a partial custom mapping with MapStruct? - java

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.

Related

MapStruct Is there a way to define defaut enum strategy for unknown value at class level?

I'm looking to define at mapper class level, a strategy that said if an enum value is not mapped, to map it to null.
Just like i would write at method level:
#ValueMapping( source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL )
So that it applies to all sub mapping method automaticaly generated by mapstruct, without forcing me to declare all of them just to add this line.
Create a 'class' mapper for your enum and then use it in other mapper with the uses attribute of the #Mapper annotation.
For example :
public interface MyEnumMapper {
#ValueMapping( source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL )
public String asString(MyEnum myenum);
}
and then to use it
#Mapper(uses=MyEnumMapper.class)
public class CarMapper {
CarDto carToCarDto(Car car);
}
(assuming Car object has an attribute of type MyEnum)
see the full documentation here : https://mapstruct.org/documentation/stable/reference/html/#invoking-other-mappers

How to create or enhance a custom annotation that uses mapstruct

Mapsturct has #Mapping annotation with predefined attributes
eg: #Mapping(source="", target="", qualifiedByName="") , what if i want to add another attribute it and use it to compute the logic
eg: #Mapping(source="", target="", qualifiedByName="" , version="")
I would like to pass the version number to it and depending on the version, it would set the target from source.
I tried to create a custom annotation and use #Mapping in it but didnt help
#CustomMapping(version = "1.0", mapping = #Mapping(source = "", target = "", qualifiedByName=""))
Why don't you just define a 2nd distinct annotation to be used alongside #Mapping?
#Mapping(source="", target="", qualifiedByName="")
#MappingVersion("1.0")
CarDto carToCarDto(Car car);
If your concern is that you want some kind of enforcement that version is always present when #Mapping is used then you could trivially write an annotation processor which does this at compile-time.
Based on the comments in the question I understand a bit more what needs to be done. Basically based on some input parameter different mapping methods need to be created.
Not sure how complex your logic is. However, what you can do is to create your own annotation processor that would be able to create MapStruct mappers.
Let's imagine that you have
#CustomMapper
public interface MyMapper {
#CustomMapping(version = "1.0", mappings = {
#Mapping(source = "numberOfSeats", target = "seatCount", qualifiedByName="")
})
#CustomMapping(version = "2.0", mappings = {
#Mapping(source = "numberOfSeats", target = "seats", qualifiedByName="")
})
CarDto map(Car car, String version);
}
So your annotation processor would need to handle CustomMapper.
The processor would also generate the different MapStruct versioned interfaces.
So:
#Mapper
public interface MyMapperV1 {
#Mapping(source = "numberOfSeats", target = "seatCount", qualifiedByName="")
CarDto map(Car car)
}
#Mapper
public interface MyMapperV2 {
#Mapping(source = "numberOfSeats", target = "seats", qualifiedByName="")
CarDto map(Car car)
}
And additionally an implementation of MyMapper. That looks like:
public class MyMapperImpl {
protected MyMapperV1 myMapperV1 = Mappers.getMapper(MyMapperV1.class):
protected MyMapperV2 myMapperV2 = Mappers.getMapper(MyMapperV2.class):
public CarDto map(Car car, String version) {
if ("1.0".equals(version)) {
return myMapperV1.map(car);
} else {
return myMapperV2.map(car);
}
}
}
Basically the goal is to have your processor generate the interfaces that would be picked up by MapStruct in the same compilation round. This is possible with annotation processing.
Another option is to write the MapStruct mappers on your own and in the caller place pick the one which is appropriate for the version. This might be simpler actually.

MapStruct Map Object to List

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

ModelMapper - How to use map(source, destination.class, typeMapName)?

I am using a ModelMapper in my java application to transform a DTO into a POJO.
I have a class MyObjectDto that must be turned into MyObject to be used as an entity and sent to the database.
I have a use case where I receive MyObjectDto with an id property, which will tell my ORM that I must update the existing entity in database. This use case works fine.
Now I have another use case where I receive the same MyObjectDto except I want to create a new one in the database. One thing that could work, I thought, would be to use a specific mapping where I skip the id property to have my ORM understand this is a create operation and not an update.
I thought the method below would solve my problem:
public <D> D map(Object source,
Class<D> destinationType,
String typeMapName)
Except I don't know how to use it. So far I tried to do this:
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
TypeMap<MyObjectDto, MyObject> typeMapForNew = modelMapper.createTypeMap(MyObjectDto.class, MyObject.class)
.addMappings(mapping -> mapping.skip(MyObject::setId));
return modelMapper;
}
This method is inside a configuration. Then I thought I'd call it like so but it gives a normal mapping:
myObject = modelMapper.map(myObjectDto, MyObject.class, "typeMapForNew");
Thanks for reading.
EDIT: for this specific example, I did wrong in the configuration class. I should have done like this:
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.createTypeMap(MyObjectDto.class, MyObject.class, "typeMapForNew")
.addMappings(mapping -> mapping.skip(MyObject::setId));
return modelMapper;
}
Then I can use the named mapping and it works. I now need to understand how I can skip ID fields of my object sub entities. But the original question is answered.
Following REST guidelines, you should not send id in your DTO at all.
Create:
#POST
/rest/items
{ ...dto... } -> mapper -> Entity with null id -> save
Update:
#PUT
/rest/items/{id}
{ ...dto... } -> mapper -> Entity with null id -> set id from URL -> save

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