Map Nested elements - Mapstruct - java

I'm trying to map following source classes to target class using MapStruct.
Target Classes :
public class Response {
private List<Customer> customer = new ArrayList<Customer>();
}
public class Customer {
private String customerId;
private List<Product> products = new ArrayList<Product>();
}
public class CustProduct {
private String CustProductId;
private String CustPdtName;
private List<productDetail> CustProductDetails = new ArrayList<productDetail>();
}
Source Classes :
public class UserList {
protected List<User> user;
}
public class User {
protected String userId;
protected List<String> productRefId; //List of products for that particular user
}
public class ProductList {
protected List<Product> product;
}
public class Product {
protected String productId; //Reference to productRefId
protected String productName;
protected List<Details> productDetails;
}
Mapper Interface :
List<Customer> mapUser(List<User> user);
#Mappings({
#Mapping(target = "customerId", source = "userId”),
#Mapping(target = "products", ignore = true)
})
Customer mapUser(User user);
#Mappings({
#Mapping(target = "CustProductId", source = "productId"),
#Mapping(target = "CustPdtName", source = "productName"),
#Mapping(target = "CustProductDetails", source = "productDetails")
})
CustProduct mapUser(Product product);
My problem is, I want to connect CustProduct with Customer
For that, I tried AfterMapping like :
default void findProducts(User user, #MappingTarget Customer customer) {
List<String> productIds = user.getproductRefId();
List<CustProduct> custProducts = new ArrayList<>();
for(int i=0; i<productIds.size();i++){
CustProduct custProduct = new CustProduct();
custProduct.setCustProductId(productIds.get(i));
//Here I want set productName and productDetails to custProduct Object(Iterating through ProductList and get from Product)
custProducts.add(custProduct);
}
}
customer.setCustProducts(custProducts);
}
Can anyone please help to fill out the comment section above?
Or is there any other option to map these objects?
Edited : I tried the below solution but the interface implementation class itself changed.

You need to use #Context annotation to bring ProductList object into the context.
Change the mapper method to below definition and pass ProductList object when calling mapUser:
#Mappings({
#Mapping(target = "customerId", source = "paxJourneyType.paxJourneyID”),
#Mapping(target = "products", ignore = true)
})
Customer mapUser(User user, #Context ProductList productList);
and then you can use the same ProductList object in #AfterMapping method :
default void findProducts(User user, #Context ProductList productList #MappingTarget Customer customer) {
List<String> productIds = user.getproductRefId();
List<CustProduct> custProducts = new ArrayList<>();
for(int i=0; i<productIds.size();i++){
CustProduct custProduct = new CustProduct();
custProduct.setCustProductId(productIds.get(i));
Product product = getProduct(ProductList productList,productIds.get(i));
custProduct.setCustPdtName(product.getProductName);
custProducts.add(custProduct);
}
}
customer.setCustProducts(custProducts);
}
private Product getProduct(ProductList productList,String productId){
//Iterate through ProductList and get from Product
}

You can do it without #AfterMapping but you will need to help MapStruct a little bit:
#Mapper
public interface CustMapper {
#Mapping(target = "customerId", source = "userId")
#Mapping(target = "products", source = "productRefIds")
Customer map(User user, #Context Map<String, Product> productsMap);
List<CustProduct> map(List<String> productRefIds, #Context Map<String, Product> productsMap);
default CustProduct map(String productId, #Context Map<String, Product> productsMap) {
return map(productsMap.get(productId));
}
#Mapping(target = "custProductId", source = "productId")
#Mapping(target = "custProductName", source = "productName")
#Mapping(target = "custProductDetails", source = "productDetails")
CustProduct map(Product product);
CustProductDetail map(ProductDetail productDetail);
}
alternatively, you can iterate over productRefIds manually:
#Mapper
public interface CustMapper {
#Mapping(target = "customerId", source = "userId")
#Mapping(target = "products", source = "productRefIds")
Customer map(User user, #Context Map<String, Product> productsMap);
default List<CustProduct> map(List<String> productRefIds, #Context Map<String, Product> productsMap) {
return productRefIds.stream().map(productsMap::get).map(this::map).collect(Collectors.toList());
}
#Mapping(target = "custProductId", source = "productId")
#Mapping(target = "custProductName", source = "productName")
#Mapping(target = "custProductDetails", source = "productDetails")
CustProduct map(Product product);
CustProductDetail map(ProductDetail productDetail);
}
In both scenarios you will need to handle somehow the situation, when productId is not present in the productsMap.
The advantage of not using #AfterMapping is that target classes can be immutable.

Your #AfterMapping method doesn't works because the #MappingTarget should be of builder type
#AfterMapping
default void findProducts(User user, #MappingTarget Customer.CustomerBuilder customer) {
...
}

Related

how can we ignore the collections in MapStructs target response

how can we ignore the collections in target response.
public class ClassSource{
private int id;
}
Destination :
public class ClassDestination {
private int id;
#JsonProperty("instructions")
#Valid
private List<Instructions> instructions = new ArrayList<>();
}
in Target want to ignore instructions list completely in the result but its returning empty array...how can we ignore the collections.
Output :
"id": "1245",
"instructions": []
expecting output like,
Output :
"id": "1245"
#Mapper(componentModel = "spring" , unmappedTargetPolicy = ReportingPolicy.WARN)
public interface PersonMapper {
#Mapping(target = "instructions, expression = "java(null)"")
ClassDestination map(ClassSource source);
}
attempt2:
public interface PersonMapper {
#Mapping(target = "instructions, igonore =true")
ClassDestination map(ClassSource source);
}
As others suggest to you, you obtain an empty list instead of null because it is initialized as a new ArrayList in the ClassDestination.
If you don't want to remove the initialization from ClassDestination, but still want a null value, maybe you can try:
#Mapper(componentModel = "spring" , unmappedTargetPolicy = ReportingPolicy.WARN)
public interface PersonMapper {
#Mapping(target = "instructions", ignore = true)
ClassDestination map(ClassSource source);
#AfterMapping
default void setNullValue(ClassSource source, #MappingTarget ClassDestination destination) {
destination.setInstructions(null);
}
}

Transform Entity-object into an Object with framework the MapStruct

I have an entity that gets "userType" during a query to the database.
public class OrderEntityXml {
#Id
#SequenceGenerator(name = "jpa.Sequence.t.order", sequenceName = "T_ORDER_SEQ", allocationSize = 1)
#GeneratedValue(generator = "jpa.Sequence.t.order", strategy = GenerationType.SEQUENCE)
private Long id;
private String customer;
#Type(type = "Order")
#Column(name = "order_xml")
private Order order;
public OrderEntityXml() {
}
There is an container for getting from an entity.
This container is using by Jackson.
This container also is using marshaller and unmarshaller
#XmlRootElement
#JacksonXmlRootElement(localName = "order")
public class Order implements Serializable {
private String customer;
#XmlElement(name = "orderItem")
#JacksonXmlProperty(localName = "orderItem")
#JacksonXmlElementWrapper(useWrapping = false)
private List<OrderItem> orderItems = new ArrayList<>();
public Order() {
}
public class OrderItem {
private String sku;
private Double price;
public OrderItem() {
}
I have set up a MapStruct.
CycleAvoidingMappingContext - ( This is necessary to avoid Cycling (and the appearance of stack overflow))
#Component
public class CycleAvoidingMappingContext {
private Map<Object, Object> knownInstances = new IdentityHashMap<Object, Object>();
#BeforeMapping
public <T> T getMappedInstance(Object source, #TargetType Class<T> targetType) {
T t = (T) knownInstances.get(source);
return t;
}
#BeforeMapping
public void storeMappedInstance(Object source, #MappingTarget Object target) {
knownInstances.put( source, target );
}
}
public interface CommonMapper<D, E> {
D toDto(E e, #Context CycleAvoidingMappingContext context);
E toEntity(D d, #Context CycleAvoidingMappingContext context);
Iterable<D> toListDto(Iterable<E> entityList);
Iterable<E> toListEntity(Iterable<D> dtoList);
}
MapperUtils - (This is utils for MapStruct)
public static Order convertToDto(OrderEntityXml orderEntityXml){
Order order = new Order();
String customer = orderEntityXml.getCustomer();
order.setCustomer(customer);
Order xmlOrder = orderEntityXml.getOrder();
List<OrderItem> orderItems = xmlOrder.getOrderItems();
order.setOrderItems(orderItems);
return order;
}
public static OrderEntityXml convertToEntity(Order order){
OrderEntityXml orderEntityXml = new OrderEntityXml();
String customer = order.getCustomer();
orderEntityXml.setCustomer(customer);
List<OrderItem> orderItems = order.getOrderItems();
Order orderInEntity = new Order();
orderInEntity.setOrderItems(orderItems);
orderInEntity.setCustomer(customer);
orderEntityXml.setOrder(orderInEntity);
return orderEntityXml;
}
OrderDtoMapper - It is basic interface for me, for classes generation
#Mapper(componentModel = "spring")
public interface OrderDtoMapper extends CommonMapper<Order, OrderEntityXml> {
#Override
default Order toDto(OrderEntityXml orderEntityXml, CycleAvoidingMappingContext context) {
return convertToDto(orderEntityXml);
}
#Override
default OrderEntityXml toEntity(Order order, CycleAvoidingMappingContext context) {
return convertToEntity(order);
}
#Override
default Iterable<Order> toListDto(Iterable<OrderEntityXml> entityList) {
Iterable<Order> collect = StreamSupport.stream(entityList.spliterator(), false)
.map(MapperUtils::convertToDto)
.collect(Collectors.toList());
return collect;
}
}
For each data type, you will have to make your own utilities, and this is cumbersome.
Сan I remove extra code and configure it via MapStruct interfaces ?
I suspect I've complicated the code.
Сan anyone tell you what needs to be tweaked or what technology can be applied to mapStruct to make the code unified
Looking at the posted entities I don't see why you need to use CycleAvoidingMappingContext. There is no cyclic dependency between your objects.
In order to avoid the manual code you've written you can use the MapStruct #Mapping annotation to customize how certain fields need to be mapped.
So in your case it would be something like:
#Mapper(componentModel = "spring")
public interface OrderDtoMapper extends CommonMapper<Order, OrderEntityXml> {
#Override
#Mapping(target = "orderItems", source = "order.orderItems")
Order toDto(OrderEntityXml orderEntityXml, #Context CycleAvoidingMappingContext context);
#Override
#Mapping(target = "order", source = "order")
OrderEntityXml toEntity(Order order, CycleAvoidingMappingContext context);
Order cloneOrder(Order order);
}
The only customizations you need are the following:
For toDto to tell MapStruct that when you are mapping from OrderEntityXml to Order you want to map the order.orderItems into the orderItems.
For toEntity to tell MapStruct that you when you are mapping from Order to OrderEntityXml you want to map the method order parameters into the order of the `OrderEntityXml
Additionally we add Order cloneOrder(Order) so that MapStruct creates a new object when mapping between Order otherwise the same object will be used.
The customer in both cases will be automatically mapped, since it matches on both sides.
There is no need to provide custom method for the Iterable mapping because MapStruct will do that automatically for you. It knows

Passing additional parameters to MapStruct mapper

Haven't figured out how to pass in additional arguments or an alternative.
Currently I'm mapping an Order and OrderLines. Both objects are different and need #mappings. Example I have an Order and OrderRequest object both are different and need #Mappings annotation to map the values, same with OrderLines.
I've created the following mappers
OrderMapper (uses = OrderLineMappers.class)
OrderLineMappers (uses = OrderLineMapper.class)
OrderLineMapper
So my issue is the OrderLine needs the OrderId from the Order object. However in the OrderLineMapper it's passing in the OrderLine and not the Order. How can I send the OrderId to the OrderLineMapper? Currently I have the OrderMapper doing an #AfterMapper, looping through the orderLines and populating with the OrderId.
Any help would be great.
Class OrderMapper
#Mapper(componentModel = "spring", uses = {OrderLineMappers.class})
public abstract class OrderMapper {
#AfterMapping
protected void orderRequestFromOrder( Order order, #MappingTarget
OrderRequest orderRequest ) {
//Wanting to do this at the OrderLineMapper class and not here
String orderId = order.getId();
List<OrderLineRequest> lines = orderRequest.getOrderLines();
List<OrderLineRequest> updatedLines = new ArrayList<>();
for (OrderLineRequest line : lines) {
line.setOrderId(orderId);
updatedLines.add(line);
}
orderRequest.setOrderLines(updatedLines);
}
#Mappings({
#Mapping( source = "orderId", target = "id" ),
#Mapping( source = "orderNumber", target = "order_number" ),
#Mapping( source = "orderLines", target = "orderLines")
})
public abstract Order orderRequestToOrder(OrderRequest orderRequest);
Class OrderLineMappers
#Mapper(componentModel = "spring", uses = {OrderLineMapper.class})
public interface OrderLineMappers {
List<OrderLine> orderLines(List<OrderLineRequest> orderLineRequest);
#InheritInverseConfiguration
List<OrderLineRequest> orderLineRequests(List<OrderLine> orderLine);
}
Class OrderLineMapper
#Mapper(componentModel = "spring")
public abstract class OrderLineMapper {
#Mappings({
#Mapping( target = "orderId", source = "orderLineId" ),
#Mapping( target = "order_line_number", source = "orderLineNumber")
})
public abstract OrderLine orderLineRequestToOrderLine(OrderLineRequest orderLineRequest);
}
Again just trying to pass in the OrderId to the OrderLineMapper. Not sure how to do this.
You can't really do this. What you can do instead is to use the #Context and perform your logic in it. You can have #AfterMapping and #BeforeMapping in your context where you can store the Order id and use that during its execution
e.g.
public class OrderMappingContext {
protected String currentOrderId;
#BeforeMapping
public void startMappingOrder(#MappingTarget Order order, OrderRequest request) {
this.currentOrderId = request.getId();
}
#AfterMapping
public void finishOrderMapping(#MappingTarget Order order) {
this.currentOrderId = null;
}
#AfterMapping
public void afterOrderLineMapping(#MappingTarget OrderLine orderLine) {
orderLine.setOrderId(this.currentOrderId);
}
}
Your mappers will then look like:
#Mapper(componentModel = "spring", uses = {OrderLineMappers.class})
public abstract class OrderMapper {
#Mapping( source = "orderId", target = "id" ),
#Mapping( source = "orderNumber", target = "order_number" ),
#Mapping( source = "orderLines", target = "orderLines")
public abstract Order orderRequestToOrder(OrderRequest orderRequest, #Context OrderMappingContext context);
#Mapper(componentModel = "spring", uses = {OrderLineMapper.class})
public interface OrderLineMappers {
List<OrderLine> orderLines(List<OrderLineRequest> orderLineRequest, #Context OrderMappingContext context);
#InheritInverseConfiguration
List<OrderLineRequest> orderLineRequests(List<OrderLine> orderLine);
}
#Mapper(componentModel = "spring")
public abstract class OrderLineMapper {
#Mapping( target = "orderId", source = "orderLineId" ),
#Mapping( target = "order_line_number", source = "orderLineNumber")
public abstract OrderLine orderLineRequestToOrderLine(OrderLineRequest orderLineRequest, #Context OrderMappingContext context);
}
On the invocation side you will do something like
orderMapper.orderRequestToOrder(request, new OrderMappingContext());

How mapping to List<> with mapstruct

Hi i need mappig variables to request. How can i mapp into List.
My request looks like.
public class Request {
private String Id;
private List<Data> applicationData;
#Data
#NoArgsConstructor
#AllArgsConstructor
public static class Data {
private String data1;
private String data2;
private String data3;
}
}
and my mapper
#Mapper(componentModel = "spring")
public abstract class RequestMapper {
#Mapping(target = "Id", source = "data.Id")
#Mapping(target = "data.???.data1", source = "data.data1")
#Mapping(target = "data.???.data2", source = "data.data2")
#Mapping(target = "applicationData.???.data3", source = "data.data3")
public abstract Request map(Data variables);
}
From what I understand you want to map your values into a singleton list. You can achieve that by providing 2 new methods in your mapper.
You mapper would look like:
#Mapper(componentModel = "spring")
public abstract class RequestMapper {
#Mapping(target = "Id", source = "Id")
#Mapping(target = "data", source = "variables")
public abstract Request map(Data variables);
protected List<Data> mapToList(Data variables) {
return variables == null ? null : mapToApplication(variables);
}
//Add mappings if they are needed
protected abstract ApplicationData mapToApplication(Data variables);
}

Mapping List<String> from List<Object> using mapstruct

Hi I am getting null for List action in DTO while setting it from the Child Source class using mapstruct. Could some help me in resolving this. Please find my code here
Entity Class:
public class Source {
int id;
String name;
List<ChildSource> childSource;
//getters and setters
}
public class ChildSource {
String code;
String action;
//getters and setters
}
DestinationDTO:
public class TargetDTO{
int sNo;
String mName;
List<String> actions;
//getters and setters
}
MApper Class:
#Mapper(componentModel = "spring")
public abstract class SampleMapper {
#Mappings({
#Mapping(target = "id", source = "sno"),
#Mapping(target = "name", source = "mNAme")
})
public abstract TargetDTO toDto(Source source);
#IterableMapping(elementTargetType = String.class)
protected abstract List<String> mapStringtoList(List<ChildSource> childSource);
protected String mapChildSourceToString(ChildSource child) {
return child.getAction();
}
}
But my action list is setting as null in the target dto. Could anyone help me here please?
You can do it like this.
#Mapper(componentModel = "spring")
public abstract class SampleMapper {
#Mappings({
#Mapping(target = "id", source = "sno"),
#Mapping(target = "name", source = "mNAme"),
#Mapping(target = "actions", source = "childSource")
})
public abstract TargetDTO toDto(Source source);
protected abstract List mapStringtoList(List childSource);
protected String mapChildSourceToString(ChildSource child) {
return child.getAction();
}
}
#Mapper(componentModel = "spring")
public abstract class SampleMapper {
#Mappings({
#Mapping(target = "id", source = "sno"),
#Mapping(target = "name", source = "mNAme"),
#Mapping(target = "actions", source = "childSource")
})
public abstract TargetDTO toDto(Source source);
default String mapChildSourceToString(ChildSource child) {
return child.getAction();
}
}

Categories