New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapping Iterable<?> object to an object instead of collection #607

Open
maurofran opened this Issue Jul 28, 2015 · 6 comments

Comments

Projects
None yet
6 participants
@maurofran
Copy link

maurofran commented Jul 28, 2015

Mapping an java.lang.Iterable object (e.g. org.springframework.data.domain.Page) to an object it's not possible because the target object must implement the Iterable interface.
If the target object implement the Iterable interface, other fields are not mapped.

@agudian

This comment has been minimized.

Copy link
Member

agudian commented Jul 28, 2015

Could you give us an example of what you have and what you would expect to be generated?

@maurofran

This comment has been minimized.

Copy link
Author

maurofran commented Aug 6, 2015

The example is the following (using a standard spring data class):

import org.springframework.data.domain.Page;

@Mapper
public interface CustomerMapper {
    List<CustomerDTO> mapCustomerListToDTO(List<Customer> customers);

    PageDTO<CustomerDTO> mapCustomerPageToDTO(Page<Customer> customers);
}

where PageDTO is something like this:

public class PageDTO<T> {
    private List<T> content;
    private long number;
    private long size;
    private long totalElements;
    private long totalPages;

    // Getters and setters...
}

and Page is:

public interface Slice<T> extends Iterable<T> {
    int getNumber();
    int getSize();
    List<T> getContent();

    // Other properties...
}

public interface Page<T> extends Slice<T> {
    int getTotalPages();
    int getTotalElements();
}

and the expected code should be

public class CustomerMapperImpl implements CustomerMapper {
    @Override
    public List<CustomerDTO> mapCustomerListToDTO(List<Customer> customers) {
        // Standard implementation of method
    }

    @Override
    public PageDTO<CustomerDTO> mapCustomerPageToDTO(Page<Customer> customers) {
        if (customers == null) {
            return null;
        }

        PageDTO<CustomerDTO> pageDTO = new PageDTO<>();

        pageDTO.setSize(customers.getSize());
        pageDTO.setNumber(customers.getNumber());
        pageDTO.setTotalElements(customers.getTotalElements());
        pageDTO.setTotalPages(customers.getTotalPages());
        pageDTO.setContent(mapCustomerListToDTO(customers.getContent());

        return pageDTO;
    }
}

The problem is that Page<T> extends Iterable<T> and the framework tries to handle as a collection, complaining me that the result is not a collection type.

@eXamadeus-zz

This comment has been minimized.

Copy link

eXamadeus-zz commented Jun 19, 2017

I also am running into this issue. Maybe there would be a way to build an @IgnoreIterable annotation that could be placed on the mapping method? Or @MapAsObject...

import org.springframework.data.domain.Page;

@Mapper
public interface CustomerMapper {
    List<CustomerDTO> mapCustomerListToDTO(List<Customer> customers);

    @IgnoreIterable
    PageDTO<CustomerDTO> mapCustomerPageToDTO(Page<Customer> customers);
}
@filiphr

This comment has been minimized.

Copy link
Member

filiphr commented Jun 19, 2017

Maybe we can reuse the already present @BeanMapping annotation for this. And if it is there then the check will not occur, i.e. it will be allowed to map non-iterable to iterable and vice-versa.

Also there is a workaround for this (I think due to a bug 😄). The check is only done between the first source parameter and the result type. You will need a wrapper type though, in order to be able to use @Mapping and multiple parameters. Which means that the following will work:

public class Wrapper<T> {
    private T value;
    //getters and setters
}

public interface MyMapper {

    @Mapping(source = "customers", target = "value")
    Wrapper<PageDTO<CustomerDTO>> map(Integer dummy, Page<Customer> customers);

}

The check will be done between Integer and Wrapper and it'll be allowed. In order not to expose the dummy you can do something like:

public abstract class MyMapper {

    public PageDTO<CustomerDTO> map(Page<Customer> customers) {
        return map(1, customers).getValue(); //Maybe do null checks as well
    }

    @Mapping(source = "customers", target = "value")
    protected Wrapper<PageDTO<CustomerDTO>> map(Integer dummy, Page<Customer> customers);

}
@heese

This comment has been minimized.

Copy link

heese commented Sep 25, 2017

Why do you need PageDTO? I think Springs' Page works quite well as DTO object (e.g., easily serialisable to JSON). Although MapStruct does not support the mapping of Page directly a custom mapping is quickly written.

@Mapper
public interface CustomerFileMapper {
  CustomerDTO toRest(Customer entity);

  default Page<CustomerDTO> toRest(Page<Customer> page) {
    return page.map(this::toRest);
  }
}
@fabriciolemos

This comment has been minimized.

Copy link

fabriciolemos commented Apr 19, 2018

One issue serializing Page would be, if the client is a Java application, Jackson will need a concrete class to deserialize it, since Page is an interface. PageImpl, the implementing class, does not have a default constructor and Jackson would have a problem with it.
Even though there's a workaround for this, it would be nice if MapStruct could support this use case of Iterable mapping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment