1. Introduction
MapStruct is a code generation tool that simplifies mapping between Java bean types. In this article, we’ll explore how to use conditional mapping with MapStruct and look at different configurations to achieve it.
2. Conditional Mapping With MapStruct
When mapping data between objects, we often find the need to map an attribute based on certain conditions, MapStruct offers a few configuration options to achieve this.
Let’s examine an instance of a target License object that requires mapping attributes based on a few conditions:
public class License {
private UUID id;
private OffsetDateTime startDate;
private OffsetDateTime endDate;
private boolean active;
private boolean renewalRequired;
// getters and setters
}
The input LicenseDto contains an optional startDate and endDate:
public class LicenseDto {
private UUID id;
private LocalDateTime startDate;
private LocalDateTime endDate;
// getters and setters
}
The mapping rules from LicenseDto to License are:
- startDate – If the input LicenseDto doesn’t have startDate in the request, let’s set startDate as the current date
- endDate – If the input LicenseDto doesn’t have endDate in the request, let’s set endDate as one year from the current date
- active – If the endDate is in the future, we will set this to true
- renewalRequired – If the endDate is in the next two weeks, we will set this to true
Let’s explore some of the ways we can achieve this by using the configurations provided by MapStruct.
2.1. Using Expressions
MapStruct provides the capability to use any valid Java expression inside a mapping expression to generate the mapping. Let’s utilize this feature to map startDate:
@Mapping(target = "startDate", expression = "java(mapStartDate(licenseDto))")
License toLicense(LicenseDto licenseDto);
We can now define the method mapStartDate:
default OffsetDateTime mapStartDate(LicenseDto licenseDto) {
return licenseDto.getStartDate() != null ?
licenseDto.getStartDate().atOffset(ZoneOffset.UTC) : OffsetDateTime.now();
}
On compilation, MapStruct generates the code:
@Override
public License toLicense(LicenseDto licenseDto) {
License license = new License();
license.setStartDate( mapStartDate(licenseDto) );
// Rest of generated mapping...
return license;
}
Alternatively, without using this method, the ternary operation inside the method can be directly passed into the expression as it’s a valid Java expression.
2.2. Using Conditional Expressions
Similar to an expression, a condition expression is a feature in MapStruct that allows the mapping of an attribute based on a conditional expression inside a string as Java code. The generated code contains the condition inside an if block, Therefore, let’s utilize this feature to map renewalRequired in the License:
@Mapping(target = "renewalRequired", conditionExpression = "java(isEndDateInTwoWeeks(licenseDto))", source = ".")
License toLicense(LicenseDto licenseDto);
We can pass in any valid Java boolean expression inside the java() method. Let’s define the isEndDateInTwoWeeks method inside the mapper interface:
default boolean isEndDateInTwoWeeks(LicenseDto licenseDto) {
return licenseDto.getEndDate() != null
&& Duration.between(licenseDto.getEndDate(), LocalDateTime.now()).toDays() <= 14;
}
On compile, MapStruct generates the code to set renewalRequired if this condition is satisfied:
@Override
public License toLicense(LicenseDto licenseDto) {
License license = new License();
if ( isEndDateInTwoWeeks(licenseDto) ) {
license.setRenewalRequired( isEndDateInTwoWeeks( licenseDto ) );
}
// Rest of generated mapping...
return license;
}
It’s also possible to set the value of an attribute from the source when a condition matches. In such instances, the mapper will populate the desired attribute with the corresponding value from the source.
2.3. Using Before/After Mapping
In certain situations, if we want to modify the object before or after mapping through customization, we can make use of the @BeforeMapping and @AfterMapping annotations from MapStruct. Let’s use this feature to map endDate:
@Mapping(target = "endDate", ignore = true)
License toLicense(LicenseDto licenseDto);
We can define the AfterMapping annotation to map endDate conditionally. In this way, we can control the mapping based on specific conditions:
@AfterMapping
default void afterMapping(LicenseDto licenseDto, @MappingTarget License license) {
OffsetDateTime endDate = licenseDto.getEndDate() != null ? licenseDto.getEndDate()
.atOffset(ZoneOffset.UTC) : OffsetDateTime.now()
.plusYears(1);
license.setEndDate(endDate);
}
We need to pass both the input LicenseDto and the target License object as parameters to this afterMapping method. Consequently, this ensures that MapStruct generates the code that invokes this method as a final step of mapping, before returning the License object:
@Override
public License toLicense(LicenseDto licenseDto) {
License license = new License();
// Rest of generated mapping...
afterMapping( licenseDto, license );
return license;
}
Alternatively, we can use the BeforeMapping annotation instead to achieve the same result.
3. Conclusion
In this article, we explored different ways to map attributes between Java bean types conditionally using MapStruct.
As always, the source code for the examples is available over on GitHub.
Find A Teacher Form:
https://docs.google.com/forms/d/1vREBnX5n262umf4wU5U2pyTwvk9O-JrAgblA-wH9GFQ/viewform?edit_requested=true#responses
Email:
public1989two@gmail.com
www.itsec.hk
www.itsec.vip
www.itseceu.uk
Leave a Reply