Get rid of attributes prefix in @Mapping

1,198 views
Skip to first unread message

pierre.fo...@gmail.com

unread,
Jan 12, 2018, 4:28:22 AM1/12/18
to mapstruct-users
Good day to you mapstruct users !

I am trying to remove some boilerplate code in our mappers but I am unable to find a solution that looks good to me. I did my research on stackoverflow, on this group and in the code.
Let's say we have 2 classes CustomerDTO and Customer having identical attributes with one tiny difference : a prefix 'customer' for all attributes of the dto. (I cannot change that, and we can expect more to come in the same way)
Is there a way to avoid declaring the @Mapping which only differs by a prefix and instead declaring this prefix more globally so that the default mapping can kick in ?

Mapping

CustomerDTO <--> Customer
customerId id
customerName name
[...]

Current Implementation

@Mapper(...)
public interface CustomerMapper {


   
@Mapping(source = "customerId", target = "id")
   
@Mapping(source = "customerName", target = "name")
   
[...]
   
CustomerDTO record2dto(Customer customer);
}


Idea

I looked into the SPI feature but I am not 100% happy with my idea.
From my understanding I should be able to add/remove the property prefix in the method org.mapstruct.ap.spi.DefaultAccessorNamingStrategy#getPropertyName at the right time by checking the enclosedElements and find the class where the prefix could be defined. I see one issue with this approach as the prefix would be defined in the class (custom annotation would be enough, but it is not the right place to put that) or calculated after scanning the fields of the class (would be quite obscure).
Is there a standard way to achieve what I am trying?

Thanks in advance!

Filip Hrisafov

unread,
Jan 14, 2018, 6:33:53 AM1/14/18
to mapstruct-users
Hi Pierre,

Your idea is the correct approach to solve this. You can define a custom Accessor Naming Strategy and provide an implementation for `getPropertyName`. You could define your own custom annotation that you would annotate your DTO and have the prefix there, or you could even say that if you have a CustomerDTO the prefix would be lowercase before the DTO (not sure how they are for you).

Your implementation can be something like:

@Retention( RetentionPolicy.SOURCE )
@Documented
@Target( ElementType.TYPE )
public @interface Prefix {
String value();
}

And your accessor naming strategy can look like:

public class PrefxingNamingStrategy extends DefaultAccessorNamingStrategy {

@Override
public String getPropertyName(ExecutableElement element) {
String propertyName = super.getPropertyName( element );
Element parent = element.getEnclosingElement();

Prefix prefix = parent.getAnnotation( Prefix.class );
if ( prefix != null && propertyName.startsWith( prefix.value() ) ) {
propertyName = Introspector.decapitalize( propertyName.substring( prefix.value().length() ) );
}
return propertyName;
}
}



Why do you think that annotating the class this is not the right place to do this? Currently this is the only way to achieve what you are looking for. Otherwise, changes in MapStruct are required to we can pass in information from the Mapper itself. However, this would require quite some changes within MapStruct, as currently the classes are not aware about the mapper definitions.

Cheers,
Filip

pierre.fo...@gmail.com

unread,
Jan 15, 2018, 11:36:15 AM1/15/18
to mapstruct-users
Hi Filip,

I think it is not really SRP since I define something related to mapping outside of the mapper classes but I will go with that since there is no other way.

Thanks a lot for your feedback!

Pierre

Filip Hrisafov

unread,
Jan 20, 2018, 6:03:38 AM1/20/18
to mapstruct-users
Hey Pierre,

You are right it might not be SRL, but currently that is the only way to achieve this. Also keep in mind that by using that you can perform mapping and prefix exclusion from different DTOs in multiple mappers.

Cheers,
Filip
Reply all
Reply to author
Forward
0 new messages