Cascading PropertyMaps with ModelMapper

xelamitchell picture xelamitchell · Jan 12, 2012 · Viewed 7.1k times · Source

Consider 6 POJO classes: 3 DAOs and 3 DTOs.

DAOs: "A", "B", "C"
DTOs: "One", "Two", "Three"

public class A {

    private int idOfA;
    private String name;
    private List<B> b;

    // getters, setters, etc...
}

public class B {

    private int idOfB;
    private String name;
    private List<C> c;

    // getters, setters, etc...
}

public class C {

    private int idOfC;
    private String name;

    // getters, setters, etc...
}



public class One {

    private int id;
    private String name;
    private List<Two> two;

    // getters, setters, etc...
}

public class Two {

    private int id;
    private String name;
    private List<Three> Three;

    // getters, setters, etc...
}

public class Three {

    private int id;
    private String name;

    // getters, setters, etc...
}

"A" contains a List of "B"s. "B" contains a List of "C"s.
"One" contains a List of "Two"s. "Two" contains a List of "Three"s.

"A" maps to "One" via "A2One". "B" maps to "Two" via "B2Two". "C" maps to Three via "C2Three".

public class A2One extends PropertyMap<A, One> {

    @Override
    protected void configure() {
        map().setId(source.getIdOfA());
        map().setName(source.getName());

        when(Conditions.isNotNull()).using(new BListConverter()).map(source.getBs()).setTwos(null);
    }
}

public class BListConverter extends AbstractConverter<List<B>, List<Two>> {

        @Override
        protected List<Two> convert(List<B> source) {

            List<Two> twos = new ArrayList<Two>(source.size());

            for(B b : source) {
                Two t = new Two();
                t.setId(b.getId());
                t.setName(b.getName());

                // Ignore list of Cs contained in B for now

                twos.add(t);
            }

            return twos;
        }
}

public class B2Two extends PropertyMap<B, Two> {

    @Override
    protected void configure() {
        map().setId(source.getIdOfB());
        map().setName(source.getName());

        when(Conditions.isNotNull()).using(new CListConverter()).map(source.getCs()).setThrees(null);
    }
}

B2Two and C2Three have very similar code to A2One.

So here is my question:

Currently the conversion between the List contained in "A" and "One" is done via a Custom Converter (and it works), but I'm repeating the same operation that B2Two is supposed to perform (map Bs to Twos).

Is there any way of cascading the Property Maps (A2One -> B2Two -> C2Three) when the List is to be mapped without having to write a Converter which merely reproduces what the Property Maps are doing?

Alternative 1: Creating a Custom TypeMap with Generics List, List which forward to the correct Property/Type Map.

Alternative 2: In A2One use the following for the List

mapper.addMappings(new BList2TwoList());
when(Conditions.isNotNull()).withProvider(listProvider).map().setTwos(mapper.map(source.getBs(), List.class));

Where ListProvider extends AbstractProvider returning new ArrayList()

Alternative 3: ???

Any ideas? The documentation for more involved mapping issues is very poor on the website and I am not necessarily looking for alternative frameworks. If alternatives are offered, they must allow for easy refactoring (so Dozer and Orika are basically out).

Thanks in advance

Update: As of the time of writing ModelMapper is basically dead and doesn't actually support Lists or cascading of mappings so that you would be forced to write a map and a converter for every object which can be a list also.
Furthermore, I found Orika has refactoring-friendly mappings by extending MapperBase. Moreover Orika can deal with mappings of individual beans and lists of beans transparently (using a single mapping rule for both situations).

Update 2: In Orika it is possible to do bidirectional deep nested mapping of collections using CustomMapper. This enables creation of bidirectional mapping implementations and allows them to be cascaded to other custom mappers. A2One would hold an instance of B2Two and use it transparently when mapping the "B"s contained in "A".

Answer

Michael Wiles picture Michael Wiles · Feb 3, 2016

I thought I'd just indicate that this is now possible with model mapper...

It does support lists (at least on version 0.7.5).

The propertymap to do it would look like this:

public class A2One extends PropertyMap<A, One> {

    @Override
    protected void configure() {
        map().setId(source.getIdOfA());
        map().setName(source.getName());

        map(source.getB()).setOne(null);
    }
}