How to call MapStruct method from interface in java stream - java

I recently started using the MapStruct mapping tool in a project. In the past, for mapping DTO -> Entity and vice versa I used custom mapper like:
public static CustomerDto toDto(Customer customer) {
return isNull(customer)
? null
: CustomerDto.builder()
.id(customer.getId())
.name(customer.getName())
.surname(customer.getSurname())
.phoneNumber(customer.getPhoneNumber())
.email(customer.getEmail())
.customerStatus(customer.getCustomerStatus())
.username(customer.getUsername())
.NIP(customer.getNIP())
.build();
}
In case when I was trying to get one single Optional object after all I was able to map my entity to dto in the following way:
public Optional<CustomerDto> findOneById(final long id) {
return customerRepository.findById(id).map(CustomerMapper::toDto);
}
Currently, as I mentioned before I am using mapStruct and the problem is that my mapper it's, not class, it's the interface like:
#Mapper
public interface CommentMapper {
#Mappings({
#Mapping(target = "content", source = "entity.content"),
#Mapping(target = "user", source = "entity.user")
})
CommentDto commentToCommentDto(Comment entity);
#Mappings({
#Mapping(target = "content", source = "dto.content"),
#Mapping(target = "user", source = "dto.user")
})
Comment commentDtoToComment(CommentDto dto);
}
I want to know if it possible to use somehow this interface method in stream gentle to map my value without wrapping values like:
public Optional<CommentDto> findCommentById(final long id) {
Optional<Comment> commentById = commentRepository.findById(id);
return Optional.ofNullable(commentMapper.commentToCommentDto(commentById.get()));
}
Thanks for any help.

Access the mapper like:
private static final YourMapper MAPPER = Mappers.getMapper(YourMapper.class);
final Optional<YourEntity> optEntity = entityRepo.findById(id);
return optEntity.map(MAPPER::toDto).orElse(null);
Basically we do a similar thing with enumerations
#Mapping(target = "type", expression = "java(EnumerationType.valueOf(entity.getType()))")
you can define java expressions in your #Mapping annotation
#Mapping(target = "comment", expression = "java(commentMapper.commentToCommentDto(commentRepository.findById(entity.commentId).orElse(null)))"
Otherwise you should be able to make use of a
class CommentMapper { ... }
which you automatically can refer with
#Mapper(uses = {CommentMapper.class})
your implementation will detect the commentEntity and Dto and will automatically use the CommentMapper.
A MapStruct mapper is workling like: Shit in Shit out, so remember your entity needs the commentEntity so the dto can has the commentDto.
EDIT
2nd solution could be using:
#BeforeMapping
default void beforeMappingToDTO(Entity source, #MappingTarget Dto target) {
target.setComment(commentMapper.commentToCommentDto(commentRepository.findById(entity.commentId).orElse(null)));
}

#Spektakulatius answer solved a problem.
To reach a goal I made a few steps:
First of all, I created an object of my mapper to use it in a java stream:
private CommentMapper commentMapper = Mappers.getMapper(CommentMapper.class);
In the last step I used my object commentMapper like:
public Optional<CommentDto> findCommentById(final long id) {
return commentRepository.findById(id).map(CommentMapper.MAPPER::commentToCommentDto);
}
After all that completely gave me a possibility to use my custom MapStruct mapper in stream.

Related

MapStruct set List as field

I'm using Map Struct with Lombok for mapping DTO and Entity back and forth but occurred on a case:
#Mapper(uses = {RoleMapper.class})
public interface UserMapper {
UserDto userToUserDto(User user);
default User signUpRequestDtoToUser(SignUpRequestDto dto) {
return User.builder()
.roles(dto.roleIds.stream().map(id -> Role.builder().id(id).build()).collect(Collectors.toList()))
.username(dto.getUsername())
.password(dto.getPassword())
.isEnabled(dto.getIsEnabled())
.build();
}
default UserFilter toUserFilter(UserFilterDto dto) {
return UserFilter.builder()
.isEnabled(dto.getIsEnabled())
.username(dto.getUsername())
.roles(
Objects.nonNull(dto.getRoleIds())
? dto.getRoleIds().stream().map(id -> Role.builder().id(id).build()).collect(Collectors.toList())
: Collections.emptyList())
.build();
}
}
Into other cases, I'm using annotation like this: #Mapping(target = "advisor.id", source = "advisorId") for create objects from id. It's work where parent contains one instance. But User and UserFilter has List<Role> as field.
How to replace the default method with annotation?
From what I can see in your example, I assume you can use annotations in this case as well: just create a method to map between RoleId and Role and Mapstruct will implement this method and call it method automatically when trying to map the collections of those models:
#Mapping(source = "id", target = "id")
Role mapRoleIdToRole(RoleId roleId);

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

Auto-map all fields except one, which should be passed through some other function

Using Mapstruct, how can I create a mapper which would auto-map all but one (or two, three, etc.) fields which should be passed through some custom mapping logic?
Mapper
#Mapper
public interface MyEntityMapper
{
MyEntityMapper INSTANCE = Mappers.getMapper(MyEntityMapper.class);
#Mappings(
{
#Mapping(source = "createdByPerson.id", target = "createdByPersonId"),
})
MyEventPayload toEventPayload(MyEntity entity);
}
If I have a someString field which needs some custom mapping logging to be done first, how would I do that? I see this argument option to #Mapping, but that seems a bit crazy to write java code within a string within an annotation!
I was hoping to do something like:
#MappingFor(MyEntity.class, "someString")
default String mapSomeString(String value) {
return value + " custom mapping ";
}
Update
I found #AfterMapping and used it e.g.:
#AfterMapping
public void mapSomeString(MyEntity entity, MyEventPayload payload) {
// do fancy stuff here
}
But I'm still curious if you can provide per-field after-mapping / custom-mapping functionality.
If you want to map a single field in a specific way you can use Mapping methods selection based on qualifiers.
This looks something like
#Mapper
public interface MyEntityMapper {
#Mapping(target = "someString", qualifiedByName = "myFancyMapping")
MyEventPayload toEventPayload(MyEntity entity);
#Named("myFancyMapping") // org.mapstruct.Named
default String mapSomeString(String value) {
return value + " custom mapping ";
}
}
You can also use Mapping#qualifiedBy and construct your own Qualifier (org.mapstruct.Qualifier) annotation.
This looks like:
#Qualifier // org.mapstruct.Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.CLASS)
public #interface MyFancyMapping {
}
#Mapper
public interface MyEntityMapper {
#Mapping(target = "someString", qualifiedBy = MyFancyMapping.class)
MyEventPayload toEventPayload(MyEntity entity);
#MyFancyMapping
default String mapSomeString(String value) {
return value + " custom mapping ";
}
}
Alternative
An alternative would be to do the custom mapping in an #AfterMapping or with an expression (I don't recommend using expressions, as it is error prone).
Have you looked at Expressions of MapStruct?
Here is example from Docs:
#Mapping(target = "timeAndFormat",
expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
Instead of new org.sample.TimeAndFormat... you could use your class constructor or method.
I ended up using #AfterMapping e.g.:
#AfterMapping
public void mapSomeString(MyEntity entity, MyEventPayload payload) {
// do fancy stuff here
}

Multiple sub-mappers using same input type

I'm trying to build DTOs split into sub-DTOs, taking information from the same big object. To do that, I have split the mapping logic into multiple submappers (one per sub-DTO).
When the submapper uses the same input type than the parent, it is somehow ignored in the MapperImpl generation. But when the input type is different, then it works like a charm.
public class MainDTO {
Integer id;
SubDTO1 subDTO1;
SubDTO2 subDTO2;
}
#Mapper(uses = { SubMapper1.class, SubMapper2.class })
public interface MainMapper {
MainDTO toDto(Entity entity);
}
#Mapper
public interface SubMapper1 {
SubDTO1 toDto(Entity entity); // KO
}
#Mapper
public interface SubMapper2 {
SubDTO2 toDto(OtherEntity entity); // OK
}
Generated MapperImpl :
#Component
public class MainMapperImpl implements MainMapper {
#Autowired
private SubMapper2 submapper2;
// No Submapper1 !
....
}
Any idea how I could fix this without putting the whole mapping logic into MainDTO?
Thanks for the help.
Under the assumption that OtherEntity is Entity...
I added a few Mappings. And the code seems to work as you intended.
#Mapping(target = "id", ignore = true)
#Mapping(target = "subDTO1", source = "entity")
#Mapping(target = "subDTO2", source = "entity")
MainDTO toDto(Entity entity);
This compiles to MainMapperImpl.class:
#Autowired
private SubMapper1 subMapper1;
#Autowired
private SubMapper2 subMapper2;
public MainMapperImpl() {
}
Unless i misunderstood your question this should be the intended result.
Please leave a comment if I've misunderstood you.

Categories

Resources