MapStruct : Nested Iterable to Non-Iterable mapping? - java

I found this example about Iterable to Non-Iterable mapping using Qualifier :
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-iterable-to-non-iterable
But how to make this mapping able to map nested properties (using dot annotation)?
E.g. mapping the field xyz of first element of a collection in the source object to a plain field on the target object?
The example define a Qualifier
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface FirstElement {
}
then define a Custom Mapper
public class MapperUtils {
#FirstElement
public <T> T first(List<T> in) {
if (in != null && !in.isEmpty()) {
return in.get(0);
}
else {
return null;
}
}
}
and, finally, the mapping is defined as
#Mapping(target = "emailaddress", source = "emails", qualifiedBy = FirstElement.class )
But if I would like to extract from the first element of emails collection a specific field, e.g. like I would have done with code emails.get(0).getEmailAddress?
For example, I expect to write a mapping like this :
#Mapping(target = "emailaddress", source = "emails[0].emailAddress")

You just need to change the MapperUtils
public class MapperUtils {
#FirstElement
public String firstEmailAddress(List<Person> in) {
if (in != null && !in.isEmpty()) {
return in.get(0).getEmailAddress();
}
else {
return null;
}
}
}
Basically the parameter of the Annotated method should have the Iterable that you want to map from, and the return type should be the Non-Iterable that you want to map to.
If you do not want to create a custom mapping for the mapping an alternative is to use the expression attribute.
For example:
#Mapping(target = "emailaddress", expression = "emails != null && !emails.isEmpty() ? emails.get(0).getEmailAddress() : null")
However, be careful using the expression can lead to compile time problems if you make a mistake. MapStruct does not do any check for the validity of the expression and uses it as is.

Another option is to use the below line in your mapper
This uses a helperclass to help with the conversion
#Mapping(target = "emailaddress", qualifiedByName={"helperClass", "emailsToAddress"},
source = "emails")
add the helperclass in the components used part of #Mapper
#Mapper(
componentModel="spring",
uses ={
helperClass.class
},
)
The helper class would look something like
#Component
#Named("helperClass")
public class helperClass {
#Named("emailsToAddress")
public String emailsToAddress(List<Email> emails) {
if(emails != null || !emails.isEmpty )
return emails.get(0).getAddress();
else
return null;
}

Related

How to call MapStruct method from interface in java stream

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.

Mapstruct: How to default a target String to Empty String when the Source is Null (Both fields have the same name and type) Java / Spring

I have two Objects Source and Target both with the same field names and types.
If a source field is null I would like the target to be "" (Empty String)
My Interface mapping looks like this (This is just two field, I have many)
#Mapper(componentModel = "spring", nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
public interface MyMapper {
#Mappings({
#Mapping(target="medium", defaultExpression="java(\"\")"),
#Mapping(target="origin", defaultExpression="java(\"\")")
})
public Target mapFrom(Source source)
If the Source has a value it should be copied across, if it is null in the source it should be "" in the target.
Mapstruct-1.3.0 seems to just keep everything null.
Any Idea? I would like default to be empty String for everything
You need to set the NullValuePropertyMappingStrategy (as part of the Mapper annotation) for defining how null properties are to be mapped.
See NullValuePropertyMappingStrategy.html#SET_TO_DEFAULT
The default value for String is "". You don't need to define it explicitly.
So, your mapper can simply look like this:
#Mapper(
componentModel = "spring",
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT,
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT
)
public interface MyMapper {
public Target mapFrom(Source source);
}
When your Source object has the same fields as Target object and when you want to manage all Source null values (e.g. for String) to became an empty String ("") in the Target object, you could create mapper interface from MapStruct library as below:
Step 1:
#Mapper(componentModel = "spring")
public interface SourceToTargetMapper {
Target map(Source source);
#BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
void update(Source source, #MappingTarget Target target);
}
The whole trick is to define nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT but you cannot define it in #Mapper annotation. Instead of that you had to place it as parameter in #BeanMapping annotation for update() method.
You can read more about this in MapStruct documentation.
Step 2:
Therefore you had to do one more operation in your code and use just implemented 'update()' method:
#Component
public class ClassThatUsingMapper {
private final SourceToTargetMapper mapper;
public Target someMethodToMapObjects(Source source) {
Target target = mapper.map(source);
mapper.update(source, target)
return target;
}
}
All null to empty String process takes place under mapper.update(source, target) method. After run mvn clean install for your project, you can check how it looks and how it works in target/generated-sources/annotations/...../SourceToTargetMapperImpl.java file.

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
}

How to generate an example POJO from Swagger ApiModelProperty annotations?

We are creating a REST API which is documented using Swagger's #ApiModelProperty annotations. I am writing end-to-end tests for the API, and I need to generate the JSON body for some of the requests. Assume I need to post the following JSON to an endpoint:
{ "name": "dan", "age": "33" }
So far I created a separate class containing all the necessary properties and which can be serialized to JSON using Jackson:
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyPostRequest {
private String name;
private String age;
// getters and fluid setters omitted...
public static MyPostRequest getExample() {
return new MyPostRequest().setName("dan").setAge("33");
}
}
However, we noticed that we already have a very similar class in the codebase which defines the model that the API accepts. In this model class, the example values for each property are already defined in #ApiModelProperty:
#ApiModel(value = "MyAPIModel")
public class MyAPIModel extends AbstractModel {
#ApiModelProperty(required = true, example = "dan")
private String name;
#ApiModelProperty(required = true, example = "33")
private String age;
}
Is there a simple way to generate an instance of MyAPIModel filled with the example values for each property? Note: I need to be able to modify single properties in my end-to-end test before converting to JSON in order to test different edge cases. Therefore it is not sufficient to generate the example JSON directly.
Essentially, can I write a static method getExample() on MyAPIModel (or even better on the base class AbstractModel) which returns an example instance of MyAPIModel as specified in the Swagger annotations?
This does not seem to be possible as of the time of this answer. The closest possibilities I found are:
io.swagger.converter.ModelConverters: The method read() creates Model objects, but the example member in those models is null. The examples are present in the properties member in String form (taken directly from the APIModelParameter annotations).
io.swagger.codegen.examples.ExampleGenerator: The method resolveModelToExample() takes the output from ModelConverters.read(), and generates a Map representing the object with its properties (while also parsing non-string properties such as nested models). This method is used for serializing to JSON. Unfortunately, resolveModelToExample() is private. If it were publicly accessible, code to generate a model default for an annotated Swagger API model class might look like this:
protected <T extends AbstractModel> T getModelExample(Class<T> clazz) {
// Get the swagger model instance including properties list with examples
Map<String,Model> models = ModelConverters.getInstance().read(clazz);
// Parse non-string example values into proper objects, and compile a map of properties representing an example object
ExampleGenerator eg = new ExampleGenerator(models);
Object resolved = eg.resolveModelToExample(clazz.getSimpleName(), null, new HashSet<String>());
if (!(resolved instanceof Map<?,?>)) {
// Model is not an instance of io.swagger.models.ModelImpl, and therefore no example can be resolved
return null;
}
T result = clazz.newInstance();
BeanUtils.populate(result, (Map<?,?>) resolved);
return result;
}
Since in our case all we need are String, boolean and int properties, there is at least the possibility to parse the annotations ourselves in a crazy hackish manner:
protected <T extends MyModelBaseClass> T getModelExample(Class<T> clazz) {
try {
T result = clazz.newInstance();
for(Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(ApiModelProperty.class)) {
String exampleValue = field.getAnnotation(ApiModelProperty.class).example();
if (exampleValue != null) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
setField(result, field, exampleValue);
field.setAccessible(accessible);
}
}
}
return result;
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalArgumentException("Could not create model example", e);
}
}
private <T extends MyModelBaseClass> void setField(T model, Field field, String value) throws IllegalArgumentException, IllegalAccessException {
Class<?> type = field.getType();
LOGGER.info(type.toString());
if (String.class.equals(type)) {
field.set(model, value);
} else if (Boolean.TYPE.equals(type) || Boolean.class.equals(type)) {
field.set(model, Boolean.parseBoolean(value));
} else if (Integer.TYPE.equals(type) || Integer.class.equals(type)) {
field.set(model, Integer.parseInt(value));
}
}
I might open an Issue / PR on Github later to propose adding functionality to Swagger. I am very surprised that nobody else has seemed to request this feature, given that our use case of sending exemplary model instances to the API as a test should be common.

Creating custom annotations with place-holder?

I'm creating some custom annotations. I need to create someones with "place-holders" as it is used in Spring
#Value("#{aParameter}")
or in JSF 2
#ManagedProperty(value="#{aParameter}")
I suppose that I must have a mapping somewhere (.properties or .xml file or an enum class) but I need to know to code this approach in custom annotation interface. I mean how to declare a place-holder in the annoatation interface ? and how to ensure the assignement of its value (in mapping file) when applying the annotation somewhere?
Thanks in advance.
You don't do it in the annotation declaration - you do it in the code using that annotation.
For example the #Value is declared like this:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public #interface Value {
/**
* The actual value expression: e.g. "#{systemProperties.myProp}".
*/
String value();
}
and if you trace how it's used you'll see that in org.springframework.web.bind.annotation.support.HandlerMethodInvoker class the value is fetched directly from the annotation defaultValue = ((Value) paramAnn).value(); and then resolved like this:
if (defaultValue != null) {
args[i] = resolveDefaultValue(defaultValue);
}
...
class AnnotationMethodHandlerAdapter{
...
protected Object resolveDefaultValue(String value) {
if (beanFactory == null) {
return value;
}
String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
if (exprResolver == null) {
return value;
}
return exprResolver.evaluate(placeholdersResolved, expressionContext);
}
So the logic taking care of resolving properties and such is placed in classes
that actually use read annotations and make them useful.

Categories

Resources