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.
I currently have the following controller method (it's a bit simplified to only show the relevant part):
#PostMapping(...)
...
public ResponseEntity<List<PresignedUrlsResponse>> getPresignedUrlBatch(#Valid #RequestBody PresignedUrlsRequest urlsRequest) {
List<PresignedUrlsResponse> presignedUrlResponses = urlsRequest.getRequests().stream().map(request -> {
// TODO: put this in it's own mapping
String url = this.mediaService.getPresignedUrl(request.getObjectId(), request.getBucket());
PresignedUrlsResponse response = new PresignedUrlsResponse();
response.setId(request.getId());
response.setUrl(url);
return response;
}).collect(Collectors.toList());
return ResponseEntity.ok().body(presignedUrlResponses);
}
As mentioned in the TODO, I want to simplify this controller method and add a mapper. I'm only used to mapping requests from a db call for example (in which I will get a List of entities) but not when the service method has to be called for a list of items.
Is there a best practice for this?
MapStruct supports mapping Stream to Collection and Stream to Stream.
However, in your use case you start with List and not with Stream.
You can move your entire logic in a mapper.
e.g.
#Mapper(componentModel = "spring", uses = {
PresignedUrlMappingService.class
})
public interface PresignedUrlsMapper {
List<PresignedUrlsResponse> map(List<PresignedUrlsRequest> requests);
#Mapping(target = "url", source = "request", qualifiedByName = "presignedUrl")
PresignedUrlsResponse map(PresignedUrlsRequest request);
}
Your PresignedUrlMappingService can look like:
#Service
public class PresignedUrlMappingService {
protected final MediaService mediaService;
public PresignedUrlMappingService(MediaService mediaService) {
this.mediaService = mediaService;
}
#Named("presignedUrl")
public String presignedUrl(PresignedUrlsRequest request) {
return this.mediaService.getPresignedUrl(request.getObjectId(), request.getBucket())
}
}
and finally your controller method will look like:
#PostMapping(...)
...
public ResponseEntity<List<PresignedUrlsResponse>> getPresignedUrlBatch(#Valid #RequestBody PresignedUrlsRequest urlsRequest) {
return ResponseEntity.ok().body(presignedUrlsMapper.map(urlsRequest.getRequests());
}
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 come across a situation where my mapping method has 3 parameters, and all the three are being used in deriving one of the properties of the target type.
I have created a default mapping method in the interface keeping the logic for deriving the property, now for calling this method, I could use an expression = "java( /*method call here*/ )" in the #Mapping annotation.
Is there any way to do this with any of the mapstruct annotation like #qualifiedByName, I tried commenting the annotation having expression property and used qualifiedByName, but it doesn't work :
#Mapper
public interface OneMapper {
#Mapping(target="id", source="one.id")
//#Mapping(target="qualified",expression = "java( checkQualified (one, projId, code) )")
#Mapping(target="qualified",qualifiedByName="checkQualifiedNamed")
OneDto createOne (One one, Integer projId, Integer val, String code);
#Named("checkQualifiedNamed")
default Boolean checkQualified (One one, Integer projId, Integer val, String code) {
if(one.getProjectId() == projId && one.getVal() == val && one.getCode().equalsIgnoreCase(code)) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}
Currently MapStruct does not support mapping methods with multiple source properties.
However, in your case you can use the #Context from 1.2.0. From what I understand the projId and the code are there just as helper of the mapping, and they are not used to map target properties from.
So you can do something like (It should work in theory):
#Mapper
public interface OneMapper {
#Mapping(target="id", source="one.id")
#Mapping(target="qualified", qualifiedByName="checkQualifiedNamed")
OneDto createOne (One one, #Context Integer projId, #Context String code);
#Named("checkQualifiedNamed")
default Boolean checkQualified (One one, #Context Integer projId, #Context String code) {
if(one.getProjectId() == projId && one.getCode().equalsIgnoreCase(code)) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}
Another alternative would be to extract all those properties into a separate class and pass that along (this would allow for multiple parameters of the same type).
The class would look like:
public class Filter {
private final Integer projId;
private final Integer val;
private final String code;
public Filter (Integer projId, Integer val, String code) {
this.projId = projId;
this.val = val;
this.code = code;
}
//getters
}
Your mapper will then look like:
#Mapper
public interface OneMapper {
#Mapping(target="id", source="one.id")
#Mapping(target="qualified", qualifiedByName="checkQualifiedNamed")
OneDto createOne (One one, #Context Filter filter);
#Named("checkQualifiedNamed")
default Boolean checkQualified (One one, #Context Filter filter) {
if(one.getProjectId() == filter.getProjId() && one.getVal() == filter.getVal() && one.getCode().equalsIgnoreCase(filter.getCode())) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}
You can then call the mapper like: mapper.createOne(one, new Filter(projId, val, code));
Since version 1.2 it is supported:
http://mapstruct.org/documentation/stable/reference/html/#mappings-with-several-source-parameters
For example like this:
#Mapping(source = "person.description", target = "description")
#Mapping(source = "address.houseNo", target = "houseNumber")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
UPDATE
Since Mapstruct allows to map multiple source arguments into a single target, I would advise to extract the checkQualified method from the mapper and instead compute the outcome beforehand and invoke the mapper with the result of the checkQualified method. Mapstruct is a mapping library, and does not excel in performing arbitrary logic. It's not impossible, but personally, I don't see the value it adds in your particular case.
With the logic extracted your mapper could look like this:
#Mapper
public interface OneMapper {
OneDto toOneDto(One one, Boolean qualified);
}
The mapper can be used like this:
One one = new One(1, 10, 100, "one");
boolean qualified = checkQualified(one, 10, 100, "one");
boolean notQualified = checkQualified(one, 10, 100, "two");
OneDto oneDto = mapper.toOneDto(one, isQualified);
For a full example, see: https://github.com/phazebroek/so-mapstruct/blob/master/src/main/java/nl/phazebroek/so/MapStructDemo.java
If you need to calculate a single target field based on multiple source fields from the same source object, you can pass the full source object to the custom mapper function instead of individual fields:
Example Entity:
#Entity
#Data
public class User {
private String firstName;
private String lastName;
}
Example DTO:
public class UserDto {
private String fullName;
}
... and the mapper... Instead of passing a single source (firstName):
#Mapper
public abstract class UserMapper {
#Mapping(source = "firstName", target = "fullName", qualifiedByName = "nameTofullName")
public abstract UserDto userEntityToUserDto(UserEntity userEntity);
#Named("nameToFullName")
public String nameToFullName(String firstName) {
return String.format("%s HOW DO I GET THE LAST NAME HERE?", firstName);
}
... pass the full entity object (userEntity) as the source:
#Mapper
public abstract class UserMapper {
#Mapping(source = "userEntity", target = "fullName", qualifiedByName = "nameToFullName")
public abstract UserDto userEntityToUserDto(UserEntity userEntity);
#Named("nameToFullName")
public String nameToOwner(UserEntity userEntity) {
return String.format("%s %s", userEntity.getFirstName(), userEntity.getLastName());
}
You can create a default method which calls internally mapstruct method with additional context params.in this way, you can obtain all parameters in 'qualifiedByName' part
#Mapper
public interface OneMapper {
default OneDto createOne(One one, Integer projId, Integer val, String code) {
return createOneWithContext(one,porjId,val,code
one,porjId,val,code //as context params
);
}
#Mapping(target="id", source="one.id")
#Mapping(target="qualified",source="one",qualifiedByName="checkQualifiedNamed")
OneDto createOneWithContext (One one, Integer projId, Integer val, String code
#Context One oneAsContext,
#Context Integer projIdAsContext,
#Context Integer valAsContext,
#Context String codeAsContext
);
#Named("checkQualifiedNamed")
default Boolean checkQualified (One one, #Context Integer projId, #Context Integer val, #Context String code) {
if(one.getProjectId() == projId && one.getVal() == val && one.getCode().equalsIgnoreCase(code)) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}
```
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);
}