6

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.

Futuregeek
  • 1,900
  • 3
  • 26
  • 51

3 Answers3

5

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
}
Ankit Chauhan
  • 646
  • 6
  • 20
  • I tried this one. It maps "Customer mapUser(User user)" but "List mapUser(List user)" -> this mapping is not working now. ie, I'm getting like this -> "user": [ { "productDetails": [] }, { "productDetails": [] }, ], – Futuregeek Jul 28 '20 at 16:46
  • Implementation changed from : 1. public List mapUser(List users) { ....... List list = new ArrayList( users.size() ); for ( User user : users ) { list.add( mapUser( user ) ); } Above implementation changed to for ( User user : users ) { list.add( userToCustomer( user ) ); } and userToCustomer method doesn't have any setters for user – Futuregeek Jul 28 '20 at 17:15
  • No... You will have to use pass ProductList in the mapUser method, so that it comes into the context. Try following : mapUser(user, productList) in the list.add() – Ankit Chauhan Aug 02 '20 at 09:40
  • If we modify like Customer mapUser(User user, @Context ProductList productList), then List mapUser(List user); will not work. – Futuregeek Aug 03 '20 at 03:53
3

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.

Mafor
  • 9,668
  • 2
  • 21
  • 36
0

Your @AfterMapping method doesn't works because the @MappingTarget should be of builder type

@AfterMapping
default void findProducts(User user, @MappingTarget Customer.CustomerBuilder customer) {
...
}
Avik Kesari
  • 271
  • 2
  • 13