I just want to apply the generateToken method to the Token field, but mapstruct is forcing login too, how to solve this?
#Mapper(uses = MapperGenerateToken.class)
public interface TelemarketerAccountMapper {
TelemarketerAccountMapper INSTANCE = Mappers.getMapper( TelemarketerAccountMapper.class );
#Mapping(expression = "java(MapperGenerateToken.generateToken(assistant.getLogin()))", target = "token")
#Mapping(source = "assistant.login", target = "login")
AuthenticatedTelemarketerAccount map(TelemarketerAccount assistant);
Genetated code:
#Override
public AuthenticatedTelemarketerAccount map(TelemarketerAccount assistant) {
if ( assistant == null ) {
return null;
}
AuthenticatedTelemarketerAccount authenticatedTelemarketerAccount = new AuthenticatedTelemarketerAccount();
authenticatedTelemarketerAccount.setLogin( MapperGenerateToken.generateToken( assistant.getLogin() ) );
authenticatedTelemarketerAccount.setToken( MapperGenerateToken.generateToken(assistant.getLogin()) );
return authenticatedTelemarketerAccount;
}
MapStruct has the concept of reusing user defined mappings defined in other mappers defined via Mapper#uses.
There are 2 ways that you can solve this:
Use of qualifiers
In your MapperGenerateToken#generateToken method you can use the MapStruct #Named annotation and use that in the mapping.
e.g.
public class MapperGenerateToken {
#Named("generateToken")
public static String generateToken(String login) {
// your custom logic
}
}
#Mapper(uses = MapperGenerateToken.class)
public interface TelemarketerAccountMapper {
TelemarketerAccountMapper INSTANCE = Mappers.getMapper( TelemarketerAccountMapper.class );
#Mapping(target = "token", source = "login", qualifiedByName = "generateToken")
AuthenticatedTelemarketerAccount map(TelemarketerAccount assistant);
}
this is going to generate the code that you expect.
Note: The second #Mapping is not needed since MapStruct will detect the mapping for the login automatically.
Use of Mapper#imports
If you still want to use expression then you can use Mapper#imports to tell MapStruct to only import that class and not use it to look up for mapping methods.
e.g.
#Mapper(uses = MapperGenerateToken.class)
public interface TelemarketerAccountMapper {
TelemarketerAccountMapper INSTANCE = Mappers.getMapper( TelemarketerAccountMapper.class );
#Mapping(target = "token", expression = "java(MapperGenerateToken.generateToken(assistant.getLogin()))")
AuthenticatedTelemarketerAccount map(TelemarketerAccount assistant);
}
I would strongly suggest the use of the first approach using qualifiers.
Related
I would like to map an entity to a DTO with a nested DTO using Mapstruct, in Kotlin.
I have a first DTO defined as follow:
data class FirstDto (
val something: String
)
This DTO is mapped in an entity and vice-versa using Mapstruct. Here is the Mapper:
#Mapper(componentModel = "spring")
interface FirstMapper {
fun entityToDto(entity: FirstEntity): FirstDto
fun dtoToEntity(dto: FirstDto): FirstEntity
}
And a second DTO nesting the first DTO:
data class SecondDto (
val somethingElse: String,
val firstDto: FirstDto
)
As for the first DTO, I define a Mapper using Mapstruct. But, I would like this mapper to use FirstMapper to map the nested DTO. So I should be using the uses property of the Mapper.
In Java, this would look like this: #Mapper(componentModel = "spring", uses = FirstMapper.class).
How should it be implemented using Kotlin?
It doesn't differ much. Purely syntax differences.
#Mapper(componentModel = "spring", uses = [FirstMapper::class])
interface SecondMapper {
#Mapping(source = "firstEntity", target = "firstDto")
fun entityToDto(entity: SecondEntity): SecondDto
#Mapping(source = "firstDto", target = "firstEntity")
fun dtoToEntity(dto: SecondDto): SecondEntity
}
Which generates
public class SecondMapperImpl implements SecondMapper {
private final FirstMapper firstMapper = Mappers.getMapper(FirstMapper.class);
#Override
public SecondDto entityToDto(SecondEntity entity) {
...
firstDto = firstMapper.entityToDto(entity.getFirstEntity());
somethingElse = entity.getSomethingElse();
SecondDto secondDto = new SecondDto(somethingElse, firstDto);
return secondDto;
}
#Override
public SecondEntity dtoToEntity(SecondDto dto) {
...
firstEntity = firstMapper.dtoToEntity(dto.getFirstDto());
somethingElse = dto.getSomethingElse();
SecondEntity secondEntity = new SecondEntity(somethingElse, firstEntity);
return secondEntity;
}
}
MapStruct version: 1.4.1.Final
When I am trying to map an integer to a bean, when the integer is null the target is still being created as a default object instead of null
#Mapper(componentModel = "spring", nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL)
public interface CompanyMapper { // NOSONAR
CompanyMapper INSTANCE = Mappers.getMapper(CompanyMapper.class);
#Mapping(source = "parentId", target = "parent.id")
Company toEntity(RequestCompany request);
}
The code generated
#Override
public Company toEntity(RequestCompany request) {
if ( request == null ) {
return null;
}
CompanyBuilder company = Company.builder();
company.parent( requestCompanyToCompany( request ) );
// Removed for simplicity
return company.build();
}
protected Company requestCompanyToCompany(RequestCompany requestCompany) {
if ( requestCompany == null ) {
return null;
}
CompanyBuilder company = Company.builder();
// Should verify if the parentId is null and
// return null if condition is met
company.id( requestCompany.getParentId() );
return company.build();
}
Edit: related to https://github.com/mapstruct/mapstruct/issues/1166#issuecomment-353742387
This works as intended. MapStruct can't know which properties of your source object need to be considered as crucial properties for performing the mapping.
In order to achieve what you are looking for you will have to provide your own mapping method for it.
e.g.
#Mapper(componentModel = "spring", nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL)
public interface CompanyMapper { // NOSONAR
default Company toEntity(RequestCompany request) {
if (request == null || request.getParentId() == null) {
return null;
}
return toEntity2(request);
}
#Named("ignoreForOtherMethods")
#Mapping(source = "parentId", target = "parent.id")
Company toEntity2(RequestCompany request);
}
Note: It is recommended not to use Mapper#getMapper when using the spring component model.
I have a Spring Boot project and I'm using mapstruct to map 2 objects.
And I have this king of structure, this is the first object:
ObjectA {
List<ObjectB> objectsB;
}
ObjectB {
String prId;
List<String> dtId;
}
---
The second object:
ObjectC {
List<ObjectD> objectsD;
}
ObjectD {
ObjectE objectE;
List<ObjectE> objectsE;
}
ObjectE {
String nmId;
}
And now using mapstruct I need to do this:
#Mapper(componentModel = "spring")
public interface AppMapper {
#Mappings({
#Mapping(target = "objectC.objectsD.objectE.nmId", source = "objectA.objectsB.prId"),
#Mapping(target = "objectC.objectsD.objectsE.nmId", source = "objectA.objectsB.dtId")
})
ObjectC objectAToObjectC(ObjectA objectA);
}
How can I do it? Any feedback will be apreciated! Thank you!
You have to add custom mapping method to map nested objectB to objectD and any string to ObjectE.
This should work for you given example:
#Mapper
public interface AppMapper
{
#Mapping(source = "objectsB", target = "objectsD")
ObjectC objectAToObjectC(ObjectA objectA);
#Mapping(source = "prId", target = "objectE.nmId")
#Mapping(source = "dtId", target = "objectsE")
ObjectD objectBtoObjectD(ObjectB objectB);
}
I have a UserDTO and User entity which I want to map. When creating a new User some fields (for example: password, modifiedBy) must be generated by some custom method (for example: password is randomly generated and encoded, but modifiedBy username is retrieved from security service). For this I autowire some services into the mapper. Many of them return String and MapStruct cannot understand which one to use in each case and just uses the first it found on everything that accepts String as input.
#Mapper(componentModel = "spring", uses = PasswordEncoder.class)
public interface UserMapper {
#Mapping(target = "password", qualifiedByName = "PASS")
User mapUser(UserDto dto);
#Named("PASS")
default String getPass(PasswordEncoder passwordEncoder){
return passwordEncoder.encode(some_random_generator);
}
}
This generates code that just uses method from PasswordEncoder in any setter that accepts String and getPass(...) method is not used at all.
However I need it to use my getPass(...) method on password field only.
Currently it is not possible to pass the used mapper or service to a default method. There is mapstruct/mapstruct#1637 open for that. Also you can't really do #Mapper( uses = PasswordEncoder.class ) as that would lead to all String to String to be mapped via the PasswordEncoder. What you can do though is to create your own custom PasswordEncoderMapper and use #Named on it, this way you would be in control.
This can look like this:
#Qualifier // org.mapstruct.Qualifier
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.CLASS)
public #interface EncodedMapping {
}
public class PasswordEncoderMapper {
protected final PasswordEncoder passwordEncoder;
public PasswordEncoderMapper(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
#EncodedMapping
public String encode(String value) {
return passwordEncoder.encode(value);
}
}
#Mapper(componentModel = "spring", uses = PasswordEncoderMapper.class)
public interface UserMapper {
#Mapping(target = "password", qualifiedBy = EncodedMapping.class)
User mapUser(UserDto dto);
}
Regarding the modifiedBy property. You should do that as part of an #ObjectFactory or by using an expression.
With an expression this can look like:
#Mapper(componentModel = "spring", uses = PasswordEncoderMapper.class, imports = SecurityUtils.class)
public interface UserMapper {
#Mapping(target = "password", qualifiedBy = EncodedMapping.class)
#Mapping(target = "modifiedBy", expression = "java(SecurityUtils.getCurrentUserId())")
User mapUser(UserDto dto);
}
I currently have a Map<String, String> that contains values in the form key = value and I would like to "expand" those into a real object.
Is it possible to automate that with MapStruct and how would I do that?
To clarify: The code I would write by hand would look something like this:
public MyEntity mapToEntity(final Map<String, String> parameters) {
final MyEntity result = new MyEntity();
result.setNote(parameters.get("note"));
result.setDate(convertStringToDate(parameters.get("date")));
result.setCustomer(mapIdToCustomer(parameters.get("customerId")));
// ...
return result;
}
Method 1
The MapStruct repo gives us useful examples such as Mapping from map.
Mapping a bean from a java.util.Map would look something like :
#Mapper(uses = MappingUtil.class )
public interface SourceTargetMapper {
SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );
#Mappings({
#Mapping(source = "map", target = "ip", qualifiedBy = Ip.class),
#Mapping(source = "map", target = "server", qualifiedBy = Server.class),
})
Target toTarget(Source s);
}
Notice the use of the MappingUtil class to help MapStruct figuring out how to correctly extract values from the Map :
public class MappingUtil {
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface Ip {
}
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public static #interface Server {
}
#Ip
public String ip(Map<String, Object> in) {
return (String) in.get("ip");
}
#Server
public String server(Map<String, Object> in) {
return (String) in.get("server");
}
}
Method 2
As per Raild comment on the issue related to this post, it is possible to use MapStruct expressions to achieve similar results in a shorter way :
#Mapping(expression = "java(parameters.get(\"name\"))", target = "name")
public MyEntity mapToEntity(final Map<String, String> parameters);
No note on performance though and type conversion may be trickier this way but for a simple string to string mapping, it does look cleaner.
Since version 1.5.0.Beta1 (Jul 2021) MapStruct supports mapping from Map to POJO.
Example:
#Mapper
public interface CustomerMapper {
#Mapping(target = "name", source = "customerName")
Customer toCustomer(Map<String, String> map);
}