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
Related
My domain:
public class Moral {
private String moralId;
private String socialReason;
private Framework framework;
}
public class Framework {
private String externalId;
private Set<String> identifiers;
}
public class Lab extends Framework {
private String system;
private String availability;
}
My DTO:
public class CreateLabRequest {
private String socialReason;
private Set<String> identifiers;
private String system;
private String availability;
}
My Mapper for this looks like:
#Mapping(source = "system", target = "framework.system")
#Mapping(source = "availability", target = "framework.availability")
#Mapping(source = "identifiers", target = "framework.identifiers")
Moral createLabRequestToMoral (CreateLabRequest createLabRequest);
However, I get the following error:
Unknown property "system" in type Framework for target name
"framework.system". Did you mean "framework.externalId"? Unknown
property "availability" in type Framework for target name
"framework.availability". Did you mean "framework.externalId"?
Simply, It is not Possible !
Maybe you wanted to make Framework inherits from Map ?!
Otherwise, the problem is due that you want to access some field in a class that doesn't have it !
public class Framework {
private String externalId;
private Set<String> identifiers;
}
public class Lab extends Framework {
private String system;
private String availability;
}
As it says, extends means that your Lab class inherits from Framework, that means that Lab inherits all fields that Framework has, and not the opposite.
So with that being said :
"framework.system" // cannot be accessed
Since there is no field named "system" in the framework class
However :
"lab.externalId" // CAN be accessed
Since Lab class inherits it from its parent class "Framework" eventhough there is no such field named "system" in the Lab class
More explanations about JAVA inheritance can be found here : https://www.geeksforgeeks.org/inheritance-in-java/
This is possible as follows:
#Mapping( source = ".", target = "framework" )
Moral createLabRequestToMoral( CreateLabRequest createLabRequest );
Lab createLabFramework( CreateLabRequest createLabRequest )
Since Lab extends Framework mapstruct will use createLabFramework( CreateLabRequest createLabRequest ) since it is an user defined method.
Also since all the fields are called the same it is not needed to add those #Mapping annotations.
Edit: expanding a bit about the error.
Unknown property "system" in type Framework for target name "framework.system". Did you mean "framework.externalId"? Unknown property "availability" in type Framework for target name "framework.availability". Did you mean "framework.externalId"?
This is basically caused by MapStruct not knowing that there is a Lab class available that extends Framework. MapStruct can also not know that you want to use the Lab class instead of the Framework class.
As shown above, one of the methods is manually defining an additional mapping method to notify MapStruct about this situation.
I have simple object Client
public class Client {
String externalCode;
String name;
String surname;
}
And I want to map it to nearly identical object
public class User {
String internalCode;
String name;
String surname;
}
See, I want externalCode to be mapped to internalCode. And I have a method which does that. I have marker my method with my custom #CodeMapping annotation and put that annotation to qualifiedBy parameter. So, here is my mapper.
#Mapper()
ClientMapper {
#CodeMapping
String toInternalCode(String externalCode) {
return externalCode + " internal part";
}
#Mapping(target = "internalCode", source = "externalCode", qualifiedBy = CodeMapping.class)
User toUser(Client client);
}
The problem is that name and surname fields are also mapped using toInternalCode method. Mapstruct sees that I have defined a method, which maps String to String and thinks it should be used in all cases.
Is there a way to tell Mapstruct, that if no qualifiers are specified, direct mapping should be used? Or make my own method which takes string and return it and tell Mapstruct it should use that method by default?
Most likely the toInternalCode is used by all methods because the #CodeMapping annotation is not meta annotated with #Qualifier (from org.mapstruct.Qualifier).
The #CodeMapping should be defined in the following way:
import org.mapstruct.Qualifier;
#Qualifier
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.CLASS)
public #interface CodeMapping {
}
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.
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
}
I have two different packages of User define Objects.....
1) ws.lender.dto (all Objects exists in this package are source side).
2) copl.com.dto (all Objects exists in this package are destination side).
Objects hierarchy and Objects name different in both side. I wan to
copy source side object to destination side object field by field or
via getter and setter using Reflection.
For Example
Source side Objects
package ws.lender.dto;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "CustomerAddresses", propOrder = {
"previousAddresses"
})
public class CustomerAddresses {
protected PreviousAddresses previousAddresses;
public PreviousAddresses getPreviousAddresses() {
return previousAddresses;
}
public void setPreviousAddresses(PreviousAddresses value) {
this.previousAddresses = value;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "PreviousAddresses", propOrder = {
"previousAddress"
})
public class PreviousAddresses {
#XmlElement(name = "PreviousAddress", required = true)
protected List<PreviousAddress> previousAddress;
public List<PreviousAddress> getPreviousAddress() {
if (previousAddress == null) {
previousAddress = new ArrayList<PreviousAddress>();
}
return this.previousAddress;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "PreviousAddress", propOrder = {
"streetNo",
"streetName"
})
public class PreviousAddress {
#XmlElement(name = "StreetNo", required = true)
protected String streetNo;
#XmlElement(name = "StreetName", required = true)
protected String streetName;
public String getStreetNo() {
return streetNo;
}
public void setStreetNo(String value) {
this.streetNo = value;
}
public String getStreetName() {
return streetName;
}
public void setStreetName(String value) {
this.streetName = value;
}
}
Destination side Objects
package copl.com.dto;
#javax.persistence.Entity
public class Customer implements java.io.Serializable
{
private Set<CustomerAddress> customerAddresses;
public Set<CustomerAddress> getCustomerAddresses()
{
return customerAddresses;
}
public void setCustomerAddresses(Set<CustomerAddress> customerAddresses)
{
this.customerAddresses = customerAddresses;
}
}
#javax.persistence.Entity
public class CustomerAddress implements java.io.Serializable
{
private String unitNumber;
private String streetName;
private String streetNumber;
public String getUnitNumber()
{
return unitNumber;
}
public void setUnitNumber(String unitNumber)
{
this.unitNumber = unitNumber;
}
public String getStreetName()
{
return streetName;
}
public String getStreetNumber()
{
return streetNumber;
}
public void setStreetName(String streetName)
{
this.streetName = streetName;
}
public void setStreetNumber(String streetNumber)
{
this.streetNumber = streetNumber;
}
}
I think you could use MapStruct to mapping between POJO's that has different attribute names.
But your scenario is complex, because you want to convert ws.lender.dto.CustomerAddresses to copl.com.dto.Customer, and this implies to convert a List<ws.lender.dto.PreviousAddress> contained into a ws.lender.dto.PreviousAddresses object, to a Set<copl.com.dto.CustomerAddress> contained into a copl.com.dto.Customer object.
So, I will explain step by step.
1. Convert from ws.lender.dto.PreviousAddress to copl.com.dto.CustomerAddress
To do this conversion you need an interface (MapStruct will create an instance for this) responsible for mapping from source object to destination object:
import ws.lender.dto.PreviousAddress;
import copl.com.dto.CustomerAddress;
#Mapper
public interface CustomerAddressesMapper{
CustomerAddressesMapper INSTANCE = Mappers.getMapper( CustomerAddressesMapper.class );
#Mappings(#Mapping(source = "streetNo", target = "streetNumber")
CustomerAddress previousToCustomerObject(PreviousAddress address);
}
This interface will map a PreviousAddress object to a CustomerAddress considering that streetNo attribute has to be mapped to streetNumber. There is no mapping for unitNumber attribute cause there is no source for it.
2. Convert a List<ws.lender.dto.PreviousAddress> to Set<copl.com.dto.CustomerAddress>
Now you have to add another mapping method to existing CustomerAddressesMapper interface:
Set<CustomerAddress> previousToCustomerSet(List<PreviousAddress> addresses);
This method will use the former previousToCustomerObject to convert every element of source list to destination set.
3. Convert from ws.lender.dto.CustomerAddresses to copl.com.dto.Customer
Finally, you need to add the last mapping method to CustomerAddressesMapper interface:
#Mappings(#Mapping(source = "previousAddresses.previousAddress", target = "customerAddresses")
Customer customerAddrsToCustomerObject(CustomerAddresses addresses);
This is the where you map an origin object, converting previousAddresses.previousAddress attribute to customerAddresses attribute, using former methods.
4. Using the mapper
To use the mapper you have to write some code as following:
CustomerAddressesMapper mapper = CustomerAddressesMapper.INSTANCE;
CustomerAddresses origin = //Retrieve it from anywhere
Customer dest = mapper.customerAddrsToCustomerObject(origin);
5. Setup
MapStruct is a source code generator, so you need to properly configure your pom.xml to include MapStruct dependency and to invoke this code generation. You can see how to do this here
Well I don't build and run this code, but this is the way to do it.
Hope it helps!
I have studied many objects mapping frameworks for this assignments like
Orika
MapStruct
ModelMapper
Dozer
Commons-BeanUtils
Finally I choose the Orika framework to complete that above Objects to Objects mapping. We can do the that mapping via other mapper framework, but I was like Orika framework because this framework very easy to use for mapping Objects to Objects.
I will explain step by step.
1. Create the Objects of Source side Object and Destination side Object.
like this..
Customer destination = new Customer();
CustomerAddresses source = new CustomerAddresses();
source = filledCustomerAddressesObject();
2. Constructing the DefaultMapperFactory
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
3. Mapping fields
ClassMapBuilder<CustomerAddresses, Customer> builder;
builder= mapperFactory.classMap(CustomerAddresses.class, Customer.class).constructorA();
builder.field("previousAddresses.previousAddress{streetNo}","customerAddresses{streetNumber}");
builder.field("previousAddresses.previousAddress{streetName}","customerAddresses{streetName}");
builder.register();
BoundMapperFacade<CustomerAddresses, Customer> boundMapper;
boundMapper = mapperFactory.getMapperFacade(CustomerAddresses.class, Customer.class);
destination = boundMapper.map(source, destination);
Its work fine Cheers
You can try Object Mapper better for casting or copying. The object to other classes in other packages you can add some property value like
senderClass and rvcClass
Later you can read those properties and proceed for converting the class. Probably you already have mapping ready for sender class against receiver class.
If I understand you correctly, you need a way to copy all like-named properties from one object to another. Like-named properties would be cases in which the source object has a method called something like getPropertyName() and the destination object has one called setPropertyName().
If this is right, then you want to use the copyProperties method of the BeanUtils class from the Apache Commons library. Documentation is here.
Now, in your example, you have some corresponding properties that are not like-named, such as StreetNumber and StreetNo. I'm afraid there's no easy way to handle that sort of thing automatically through reflection; you would need to define the mappings between source and target properties yourself, perhaps by defining a helper class to do the copying.