I have a personal war going against writing java code in strings. What I'm trying to achieve is 100% doable with an expression. I would, however, like to try and find a way of doing it without.
Situation:
TargetJpa {
...
Instant createdAt;
}
What I want to do:
// using 1.5.2.Final
#Mapper(...)
public interface JpaMapper {
#Mapping(target = "createdAt", qualifiedByName = "now")
TargetJpa toJpa(Srouce s);
#Named("now")
default Instant now() {
return Instant.now();
}
}
This obviously does not work. It complains that I'm missing a source. Which is correct, I don't need a source, I'm generating a value. But this can't be a constant. And I personally wish to use java code in strings as little as humanly possible.
Ofc, there is an obvious workaround, pass a dummy source value.
#Mapper(...)
public interface JpaMapper {
#Mapping(target = "createdAt", source = "s", qualifiedByName = "now")
TargetJpa toJpa(Srouce s);
#Named("now")
default Instant now(Source s) {
return Instant.now();
}
}
But this has a huge disatvantage. Had I not have to name a source, I could remove this "now" from this interface alltogeather, make a "InstantMapper" and reuse the code:
#InstantMapper
public class InstantMapperImpl {
#Now
default Instant now() {
return Instant.now();
}
}
Then use it on anything:
... uses = { ..., InstantMapperImpl.class }
...
#Mapping(target = "createdAt", qualifiedBy = {InstantMapper.class, Now.class})
TargetJpa1 toJpa(Srouce1 s);
#Mapping(target = "createdAt", qualifiedBy = {InstantMapper.class, Now.class})
TargetJpa2 toJpa(Srouce2 s);
#Mapping(target = "createdAt", qualifiedBy = {InstantMapper.class, Now.class})
TargetJpa3 toJpa(Srouce3 s);
Any way to achieve this without tying myself to some source that I simply don't need?
MapStruct currently doesn't support using a random method to map into a specific property. When using Mapping#target you have few options that you can use for the source of the mapping:
source - map from a specific source
expression - map using an expression
constant - map using some constant value
ignore - ignore the mapping
All the rest of the properties are in one way or another used for specifying some other things for the mapping.
When none of those 4 options are provided it is assumed that the value that is in target is the value in source.
e.g.
#Mapping(target = "createdAt", qualifiedByName = "now")
actually means
#Mapping(target = "createdAt", source = "createdAt", qualifiedByName = "now")
If you are interested in seeing something like you are requesting in MapStruct I would suggest raising a feature request
Related
I have a dictionaries which has multiple fields like: id, code, ruName, enName. id is a UUID, the others are Strings.
What I want is something like that:
#Mapping(source = "sourceName", target = "targetName", dictionary = "dictName", dictionaryField = "dictionaryField")
and based on target type it will generate something like that
if target type UUID
return target.targetName(getId(dictionary ,dictionaryField , sourceName));
if target type String
return target.targetName(getValue(dictionary, dictionaryField, sourceName));
What I have now is a generator which generates mappers for every dictionary and for every field in format dictionaryByFieldName, so I can use this format:
#Mapping(source="sourceName", target="targetName", qualifiedByName = "dictionaryByFieldName")
But I don't like it cos most of created mappers have no uses in project and aren't valid cos not every field is unique to get id by field-_-
Currently it is not possible to retrieve the fieldname within mapstruct, what however is possible is using an #Mapping per field to minimize the amount of mapping code.
for example:
#Mapping( target = "myUuidFieldName", expression = 'java(dict.getId("myUuidFieldName", source.getMyUuidFieldName()))' )
#Mapping( target = "myStringFieldName", expression = 'java(dict.getValue("myStringFieldName", source.getMyStringFieldName()))' )
Target map(Source source, #Context Dictionary dict);
and have a separate class called Dictionary in which you store the mappings for retrieval. This way you can easily replace the Dictionary with another Dictionary implementation in case you need a different translation.
Example of a Dictionary class:
private class Dictionary{
DbRecordAccessor dbRecord;
Map<String, RetrievalInformation> retrievalMap;
// constructor and retrievalMap initialization methods
UUID getId(String fieldName, String value){
RetrievalInformation info = retrievalMap.get(fieldName);
return dbRecord.getId(info.getDictionaryName(), fieldName, info.getDictionaryField());
}
String getValue(String fieldName, String value){
RetrievalInformation info = retrievalMap.get(fieldName);
return dbRecord.getValue(info.getDictionaryName(), fieldName, getId(fieldName, value));
}
}
The following is not (yet) supported by mapstruct. See here for more information.
It would be nice if you could have done the following:
Target map(Source source, #Context Dictionary dict);
UUID getId(String value, #TargetProperty String targetField, #Context Dictionary dict) {
return dict.getId(targetField, value);
}
I have the following mapper
#Mapper(config = MappingConfig.class)
public interface PokerRoomMapper {
#Mapping(target = "phase", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
PokerRoom pokerRoomDtoToPokerRoom(PokerRoomDto pokerRoomDto);
}
The pokerRoomDto which is passed to it has a "phase" field which can be null. I want this field to be ignored when it is null. But right now the "null" value still gets mapped to the pokerRoom entity.
If I just ignore the field in the mapper it works and the default value for phase in PokerRoom stays untouched however I dont want to always ignore it.
#Mapper(config = MappingConfig.class)
public interface PokerRoomMapper {
#Mapping(target = "phase", ignore = true)
PokerRoom pokerRoomDtoToPokerRoom(PokerRoomDto pokerRoomDto);
}
This works as designed. NullValuePropertyMappingStrategy is only applied to update method. It is not used for normal mappings.
I think that you are looking for NullValueCheckStrategy, if you use NullValueCheckStrategy#ALWAYS then MapStruct will always do a null check non the PokerRoomDto and only invoke the setter on the PokerRoom if the value was not null
If you initialise your field at declaration and want to keep that value, I've come up with a solution.
A bit hacky, not very general (depends on generated variable name), but works.
Assuming:
class PokerRoom {
Integer phase = 0;
}
You can use
#Mapping(target = "phase", defaultExpression = "java( pokerRoom.getPhase() )")
PokerRoom pokerRoomDtoToPokerRoom(PokerRoomDto pokerRoomDto);
A simpler solution would be to use the same constant you use at field declaration.
#Mapping(target = "phase", defaultValue = "0")
PokerRoom pokerRoomDtoToPokerRoom(PokerRoomDto pokerRoomDto);
So in the example below, I have 2 inputs object, being mapped to one output object.
The majority of the mappings are direct from one input, to one output, with only one coming from another object.
#Mapping(source = "input.a" target = "output.a")
#Mapping(source = "input.b" target = "output.b")
#Mapping(source = "input.c" target = "output.c")
#Mapping(source = "input.d" target = "output.d")
#Mapping(source = "extra.a" target = "output.extraa")
Output toOutputMapper(Input input, ExtraValues extra)
Is there a way to say "use this object as default", which saves me mapping the values and forgoing the automatic mapping Mapstruct provides?
Something like:
#Mapping(source = "extra.a" target = "output.extraa")
Output toOutputMapper(#Default Input input, ExtraValues extra)
When using MapStruct 1.4 you can use mapping to current target to achieve what you are looking for.
e.g.
#Mapping(source = "input" target = ".")
#Mapping(source = "extra.a" target = "output.extraa")
Output toOutputMapper(Input input, ExtraValues extra)
Given a set of five objects:
KeyDto{String id}
ValueDto{String name, String value, String description}
Key{String id, String name}
Value{String value, String description}
Target{Key key, Value value}
I would like to create a mapper with two parameters:
Target dtosToTarget(KeyDto keyDto, ValueDto valueDto);
However, just defining helper methods for Key and Value seems not to be enough:
#Mapping(source = "keyDto.id", target = "id")
#Mapping(source = "valueDto.name", target = "name")
Key keyDtoAndValueDtoToKey(KeyDto keyDto, ValueDto valueDto);
Value valueDtoToValue(ValueDto valueDto);
This gives an error on the actual dtosToTarget method:
Error:(17, 19) java: Can't map property "java.lang.String value" to "mapping.Value value". Consider to declare/implement a mapping method: "mapping.Value map(java.lang.String value)".
The only solution I could think of - is defining custom java expressions to call necessary methods, like
#Mapping(target = "key", expression = "java(keyDtoAndValueDtoToKey(keyDto, valueDto))")
#Mapping(target = "value", expression = "java(valueDtoToValue(valueDto))")
Is there a cleaner approach?
The error you are seeing is because by default MapStruct will try to map valueDto.value into Target.value which is String to Value.
However, you can configure this like this:
#Mapper
public MyMapper {
#Mapping( target = "key.id", source = "keyDto.id")
#Mapping( target = "key.name", source = "valueDto.name")
#Mapping( target = "value", source = "valueDto")
Target dtosToTarget(KeyDto keyDto, ValueDto valueDto);
Value valueDtoToValue(ValueDto valueDto);
}
Try :
#Mapping(source = "valueDto.name", target = "name")
void keyDtoAndValueDtoToKey(#MappingTarget KeyDto keyDto, ValueDto valueDto);
This will keep all fields from Key Dto as it is and mapping required fields from valueDto as configured.
I want to map UserDTO and UserGroupDTO where User has address as a list in which it has all address fields and Usergroup has individual address fields. Please let me know how could I map these fields.
There is currently no official support for that, but there is a workaround using expressions, as described in the ticket : https://github.com/mapstruct/mapstruct/issues/1321#issuecomment-339807380
This would work your case:
#Mapper
public abstract class UserDTOMapper {
#Mapping( expression = "java(userDTO.getAddress().get(0))", target = "street")
#Mapping( expression = "java(userDTO.getAddress().get(1))", target = "zipCode")
#Mapping( expression = "java(userDTO.getAddress().get(2))", target = "country")
abstract public UserGroupDTO mapTo(UserDTO userDTO);
}
But you have to make sure that the address property implemented as List will always contain the same number of fields and in correct order, else the mapping based on list index will not work as expected.