At first, let me apologize if this question was already posed. I could not find any reference in StackOverflow.
I am trying to use a qualification in my mapping between beans via MapStruct, so as to convert between lists via a qualified mapping. Alas, without success.
Let us suppose we have the following classes (simplified as much as I can, and I will omit obvious getters/setters):
public class A {
private String propertyA;
}
public class B {
private String propertyB;
private A instanceA;
}
public class C {
private List<B> instancesB;
}
public class A1 {
private String propertyA;
}
public class B1 {
private String propertyB;
private A1 instanceA1;
}
public class C1 {
private List<B1> instancesB1;
}
Let us suppose to have the following qualifier:
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD })
public #interface Full{}
Let us also suppose to have the following mappings:
#Mapper
public interface MapperA {
A1 toA1(A a);
A toA(A1 a1);
}
#Mapper
public interface MapperB {
B1 toB1(B b);
#Mapping(source="propertyA", target="propertyA1")
#Full
B1 toB1Full(B b);
#IterableMapping(qualifiedBy=Full.class)
#Full
List<B1> toB1s(List<B> bs);
toB(B1 b);
#Mapping(source="propertyA1", target="propertyA")
#Full
toBFull(B1 b);
#IterableMapping(qualifiedBy=Full.class)
#Full
List<B> toBs(List<B1> bs);
}
#Mapper
public interface MapperC {
<HERE IS THE PROBLEM>
}
How can I write the Mapper C so as to use the full mapping for the iterated instances of B?
No matter how I try to write the annotations for the mapping, such as
#Mapping(source = "instancesB1", target="instancesB", qualifiedBy=Full.class)
I always find myself with an incomplete mapped entity: the B1 instance has a correctly mapped propertyB field, but no instanceA1.
I can of course just write MapperC as an abstract class, implement the method, call manually the mapper and just be happy with it, but I'm questioning whether it is possible to just annotate somehow the method in MapperC and have Mapstruct automagically use the correct mapping method for me (since this is an oversimplified case, but I may have tens of such lists to convert).
Thanks for the attention.
Not sure if you have tried that, but looking from the example it seems like the Mapper#uses is missing.
In your case it should look something like (omitted the mapping methods):
#Mapper
public interface MapperA {
}
#Mapper(uses = MapperA.class)
public interface MapperB {
}
#Mapper(uses = MapperB.class)
public interface MapperC {
}
When you use Mapper#uses then MapStruct will look for the qualified methods in the classes defined in uses. However, if you don't have that then there is nothing MapStruct could do and would generate some default mapping.
Related
Using the MapStruct framework, how do you map multiple fields into a single one (based on custom logic) while still mapping the other fields one to one?
Here is a simple example to illustrate what I mean:
public class Source {
private String firstname;
private String surname;
// other fields eg:
private String address;
private int age;
private int favoriteNumber;
}
public class Target {
private String fullname; // Sould be firstname + surname
// other fields eg:
private String address;
private int age;
private int favoriteNumber;
}
I know it's possible using expressions:
#Mapping(target = "fullname", expression = "java(el.getFirstname() + el.getSurname())")
But in my special use case, not depicted in this example, I need to use some external library for the merging/mapping of the two fields, which isn't feasible with expressions.
Is there a way to achieve merging two fields without expressions?
One approach would be to add a custom mapping method from the Source object to the merged value, and then declare the source for the merged field to be the whole source object:
interface CustomMappingMethodMapper {
#Mapping(target = "fullname", source = ".")
Target map(Source source);
default String getFullName(Source s) {
return s.getFirstname() + " " + s.getSurname();
}
}
You can use #AfterMapping annotation
https://mapstruct.org/documentation/stable/reference/html/#customizing-mappings-with-before-and-after
You would like to replace your interface with abstract class and then
#AfterMapping
void customMapping(#MappingTarget Target target, Source source) {
// any custom logic
}
You claim that calling an external library in an expression isn't feasible. This may not be true, depending on the nature of the library you're calling and the frameworks being used.
Static method
If the method being called is a static method on a class, it can be called directly within the expression annotation element of #Mapping. To avoid having to fully qualify the class being called, the imports element of #Mapper can be used.
#Mapper(imports = ExternalLibrary.class)
public interface SourceToTargetMapper {
#Mapping(target = "fullname",
expression = "java(ExternalLibrary.toFullName(s.getFirstname(), s.getSurname()))")
Target map(Source s);
}
Spring Framework bean
If the library method is a method on a Spring bean, the mapper can be made into a bean using the Spring component model, and the Spring bean containing the library method can be injected into the mapper:
#Mapper(componentModel = "spring")
public static abstract class SourceToTargetMapper {
#Autowired
ExternalLibrary externalLibrary;
#Mapping(target = "fullname",
expression = "java(externalLibrary.toFullName(s.getFirstname(), s.getSurname()))")
abstract Target map(Source s);
}
To use this mapper, inject it as a Spring bean, and call the mapping method on the bean:
#Component
public class Example {
#Autowired
private SourceToTargetMapper mapper;
public void demonstrate(Source s) {
System.out.println(mapper.map(s));
}
}
I haven't tested it myself, but I imagine this injection approach would work with the other component models supported by MapStruct (cdi & jsr330).
I needed to compose 2 basic fields into am Wrapped Object. Here's how I did id
#Mapping(source = "quantity", target = "energyQuantity.quantity")
#Mapping(source = "quantityUnit", target = "energyQuantity.energyQuantityUnit", qualifiedByName = "tradeSearchEnergyQuantityUnitToEnergyQuantityUnit")
Trade tradeSearchToDomainDTO(api.client.generated.model.Trade trade);
where energyQuantity is a Wrapper class for the 2 fields
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
Given the following classes and a mapper that takes mulitple source arguments
(I use lombok to keep source as short as possible.)
#Getter
#Setter
public class MySourceOne {
private String src1;
}
#Getter
#Setter
public class MySourceTwo {
private String src2;
}
#Getter
#Setter
public class MyTargetObject {
private String prop1;
private String prop2;
}
#Mapper
public interface MyTargetObjectMapper {
#Mapping(target="prop1", source="a")
#Mapping(target="prop2", source="b")
public MyTargetObject mapMyObject(String a, String b);
}
#Getter
#Setter
public class MyComplexTargetObject {
private MyTargetObject myTargetObject;
}
I am trying to create a mapper for MyComplexTargetObject that will invoke implicitly the MyTargetObjectMapper .
But the "source" won't allow to map multiple parameter like this
#Mapper(uses= {MyTargetObjectMapper.class})
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject", source="one.src1, two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
So I am trying to use an expression="..." instead of source, but nothing works so far.
Any thoughts a clean way to do this without calling the MyTargetObjectMapper in a concrete method?
MapStruct does not support selection of methods with multiple sources.
However: you can do target nesting to do this.
#Mapper
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject.prop1", source="one.src1" )
#Mapping(target="myTargetObject.prop2", source="two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
And let MapStruct take care of generating the mapper. Note: you can still use a MyComplexTargetObjectMapper to do single source to target to achieve this.
I have two domain entities:
class Identity {
Long id;
Set<Business> businesses;
}
class Business {
Long id;
String name;
}
I then have two DTOs that extend a base DTO:
class BaseDto {
String id;
}
class IdentityDto extends BaseDto {
Set<BaseDto> businesses;
}
class BusinessDto extends BaseDto {
String name;
}
Then I created a mapper that maps a list of my domain entities to either a Set of the specific dto, or a set of the more generic base dto. This is because when I am getting a list of businesses, I want the full business dto, but when I get an identity, I just what the base info in it's list of businesses.
But when I try to create the mapper for the identity I get the following error:
Ambiguous mapping methods found for mapping property
"Set<Business> businesses" to Set<BaseDto>:
Set<BusinessDto> BusinessMapper.toSet(Set<Business> businesses),
Set<BaseDto> BusinessMapper.toBaseSet(Set<Business> businesses).
I thought that mapstruct used the most specific method, so should know to use the toIdentifierSet method in this case.
How do I make mapstruct know which method to use?
There is no most specific method here as you are trying to map into Set<BaseDto>.
You can use Mapping method selection based on qualifiers.
You can define some annotations:
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.CLASS)
public #interface BaseInfo {
}
Then in your BusinessMapper
#Mapper
public interface BusinessMapper {
Set<BusinessDto> toSet(Set<Business> businesses);
#BaseInfo
Set<BaseDto> toBaseSet(Set<Business> businesses);
}
Then in your identifier
#Mapper
public interface IdentifierMapper {
#Mapping(target = "businesses", qualifiedBy = BaseInfo.class)
IdentityDto map(Identity source);
}
In case you want to explicitly pick always you can add another annotation BusinessInfo and then annotate the other method. Then you would need to pick a method each time.
Suppose you have this entity:
class Foo{
String propA;
String propB;
}
and you want to serialize for one API like :
{propA: "ola",
propB: "Holla"}
and for another API like :
{fooPropA: "ola",
fooPropB: "Holla"}
How can this be achieved using jackson and using the same entity. Creating 2 different entities is not an option :)
There are several ways in which you can achieve this. You can enable a custom serializer (already covered by #se_vedem), register an annotation introspector which changes the property names for the corresponding class and so on.
However, if you are willing to only add a string prefix to all the property names, then the Jackson property name strategy is probably the best fit. The naming strategy class has the access to the serialized object type information, so you can make a decision whether to change the property name or not.
Here is an example using a custom annotation that defines the prefix:
public class JacksonNameStrategy {
#Retention(RetentionPolicy.RUNTIME)
public static #interface PropertyPrefix {
String value();
}
#PropertyPrefix("foo_")
public static class Foo {
public String propA;
public String propB;
public Foo(String propA, String propB) {
this.propA = propA;
this.propB = propB;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new MyPropertyNamingStrategyBase());
System.out.println(mapper.writeValueAsString(new Foo("old", "Holla")));
}
private static class MyPropertyNamingStrategyBase extends PropertyNamingStrategy {
#Override
public String nameForField(MapperConfig<?> config,
AnnotatedField field,
String defaultName) {
PropertyPrefix ann = field.getDeclaringClass().getAnnotation(PropertyPrefix.class);
if (ann != null) {
return ann.value() + defaultName;
}
return super.nameForField(config, field, defaultName);
}
}
}
Output:
{"foo_propA":"old","foo_propB":"Holla"}
In your API method you choose between two ObjectMapper instances one with the default naming naming strategy and one with the custom one.
You can achieve this by using modules feature from Jackson.
Basically, each API would have it's own ObjectMapper and they will be configured with different modules. This way you can create 2 serializers for the same class and register them on the appropriate module. More read can be found here http://wiki.fasterxml.com/JacksonFeatureModules
However, be aware that serializers are loaded in a particular order. First it tries to get the annotated ones, if none is found it will try to get those registered from modules. So, for example if you have your class annotated with serializer, then that serializer(FooSerializer) would be chosen instead of the one configured in module(MySecondFooSerializer).
#JsonSerialize(using = FooSerializer.class)
class Foo{
String propA;
String propB;
}
module.addSerializer(Foo.class, new MySecondFooSerializer());