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;
}
}
Related
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.
Here is a code snippet:
Source:
public class BuyerInfo {
String membershipID;
}
Target:
public class BuyerInfo {
UUID membershipID;
}
Error in console:
Can't map property "String buyerInfo.primaryMembershipID" to "UUID buyerInfo.id". Consider to declare/implement a mapping method: "UUID map(String value)".
I tried this and it worked for me.
#Mapper(componentModel = "spring")
public interface SourceToTargetMapper {
SourceToTargetMapper INSTANCE = Mappers.getMapper( SourceToTargetMapper.class );
#Mapping(target="buyerInfo.id", expression = "java(mapToBuyerInfoId(buyerMetaData))")
Order sourceOrderList(SourceOrder.Order sourceOrder);
default UUID mapToBuyerInfoId(BuyerMetaData buyerInfo){
return UUID.fromString(buyerInfo.getPrimaryMembershipID());
}
}
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 Story entity in my Spring Boot application. It has String field storyInfo which contains:
{"title":"random title", "description":"random description"}
For my Story entity I have StoryDTO with map field called storyInfo.
The question is: how can I convert String field from Strory into Map in StoryDTO using MapStruct?
Try following code, inspired from here
#Mapper(componentModel = "spring")
public interface StoryMapper {
#Mappings({
#Mapping(source = "storyInfo", target = "storyInfo", qualifiedByName = "fromJsonToMap")
})
StoryDTO toStoryDTO(Story story);
#Mappings({
#Mapping(source = "storyInfo", target = "storyInfo", qualifiedByName = "fromMapToJson")
})
Story toStory(StoryDTO storyDTO);
#Named("fromJsonToMap")
default Map<String, Object> fromJsonToMap(String storyInfo) throws IOException {
if (Objects.nonNull(storyInfo)) {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Double> result = objectMapper.readValue(storyInfo, new TypeReference<Map<String, Object>>() {});
return result;
}
return null;
}
#Named("fromMapToJson")
default String fromMapToJson(Map<String, Object> storyInfo) throws JsonProcessingException {
if (Objects.nonNull(storyInfo)) {
ObjectMapper objectMapper = new ObjectMapper();
String result = objectMapper.writeValueAsString(storyInfo);
return result;
}
return null;
}
}
Thank you guys for answers. Found the easiest solution for me by adding few manual mappers to MapStruct's StoryMapper interface.
// Manual convert to Map
default Map toMap(String text){
Map map = new HashMap();
try {
map = new ObjectMapper().readValue(text, new TypeReference<Map<String, String>>(){});
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
// Manual convery from map
default String fromMap(Map map){
return new JSONObject(map).toString();
}
The already provided answer explains well how you can provide a Service to do the mapping with Jackson.
In order to make this work with MapStruct you can use qualifiers and annotate your service accordingly.
For example
#Qualifier // from the MapStruct package
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.CLASS)
public #interface FromJson {
}
public interface StringToMapConverter {
#FromJson
Map<String, String> convert(String string);
}
#Mapper(componentModel = "spring")
public interface MyMapper {
#Mapping(target = "storyInfo", qualifiedBy = FromJson.class)
StoryDTO convert(Story story);
}
The implementation of StringToMapConverter should be as in the already provided answer. You don't have to use a dedicated interface for the converter, you an also use an abstract mapper, inject the ObjectMapper and do the rest same.
MapStruct will then use it to convert the storyInfo String into the map.
Some other possible solution, outside of the scope of the question and if you use Hibernate. You can use Map<String, String> in your entity, but still map it to String in the DB. Have a look at hibernate-types by Vlad Mihalcea, it allows using extra types so you can persist objects as JSON in the database
You can create a generic tool class so other Mapper can also use.
p.s: JsonUtil just a util clss use to transform Object to Json.
And you can use jackson, fastjson, gson.
#Component
public class MapStructUtil {
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface JsonStrToMap {
}
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface MapToJsonStr {
}
#JsonStrToMap
public Map jsonStrToMap(String jsonStr) {
return JsonUtil.toMap(jsonStr);
}
#MapToJsonStr
public String mapToJsonStr(Map<String, String> map) {
return JsonUtil.toJsonString(map);
}
}
Then you can use it in your Mapper like this
p.s: Here use componentModel = "spring", so you need to add #Componnent annotation in MapStructUtil
#Mapper(componentModel = "spring", uses = {MapStructUtil.class})
public interface StoryMapper {
#Mapping(source = "storyInfo", target = "storyInfo", qualifiedBy = JsonStrToMap.class)
StoryDTO toStoryDTO(Story story);
#Mapping(source = "storyInfo", target = "storyInfo", qualifiedBy = MapToJsonStr.class)
Story toStory(StoryDTO storyDTO);
}
I am not familiar with MapStruct, but I might suggest an alternative since you are running your application in a Spring context.
Since your string is a JSON string, your best course of action would be to use a JSON library. Spring Boot comes with its own preconfigued instance of the Jackson ObjectMapper (which you may override to add/remove specific features by defining using a #Bean of type ObjectMapper in any #Configuration class).
You might inject an instance of this using:
#Autowired
ObjectMapper objectMapper;
After that, you are able to use the object mapper to transform the string into a HashMap<String, String> (or whichever types you need) as follows:
Map<String, String> result = objectMapper.readValue(storyInfo, new TypeReference<Map<String, String>>() {});
I will try to update this answer with a MapStruct approach, but perhaps this might be more practical for you at this time.
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);
}