Advanced mapping with lookup

816 views
Skip to first unread message

Miha Valencic

unread,
Jun 6, 2013, 6:57:38 AM6/6/13
to model...@googlegroups.com
Hi!


I'm new to ModelMapper, but I like the concept so far. I have a requirement to map object to another object, with transformation & lookup.

Since I'm getting some strange exceptions, I suppose I am misusing the API. To reproduce it, I created a simple test case, where I map Person object to PersonDTO.

The idea is to map firstName, lastName, and then firstName + " " + lastName to displayName of the PersonDTO object. I've created a custom mapping:


public class CustomMap extends PropertyMap<Person, PersonDTO> {
  @Override 
  protected void configure() { 
    map().setDisplayName(source.getFirstName() + " " + source.getLastName()); 
  } 


Where classes are defined:

public class Person {
  private String firstName; 
  private String lastName;
  // getters & setters
}

public class PersonDTO extends Person { 
  private String displayName; 
  // getter & setter
}

when mapping Person to PersonDTO using code: 

    @Test
    public void mapWithCustomTypeMap()
    {
        ModelMapper mapper = new ModelMapper();
        CustomMap customMap = new CustomMap();
        mapper.addMappings(customMap);

        Person p = new Person();
        p.setFirstName("Test first");
        p.setLastName("Test last");
        PersonDTO mapped = mapper.map(p, PersonDTO.class);

        assertEquals(p.getFirstName(), mapped.getFirstName());
        assertEquals(p.getLastName(), mapped.getLastName());
        assertNull(mapped.getDisplayName());
        
        assertEquals(p.getFirstName() + " " + p.getLastName(), mapped.getDisplayName());
    }

I get several exceptions (see below - I aplogize for lengthy stackstrace). I suppose there's something I'm doing wrong? In the end, I would like to lookup the destination value in a "repository" using a key from a source object. So, the typemapper would looke something like this:  map().setX(_repo.lookup(source.getId()).getX()); ... which brings up another topic of deferred execution, but let's fix the current problem first. :)

Regards,
 Miha.


Mapping exceptions:
mapWithCustomResolver(mapper.MapperTest)  Time elapsed: 0.1 sec  <<< ERROR!
org.modelmapper.MappingException: ModelMapper mapping errors:

1) Error mapping mapper.Person to mapper.PersonDTO

1 error
at org.modelmapper.internal.Errors.throwMappingExceptionIfErrorsExist(Errors.java:337)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:79)
at org.modelmapper.ModelMapper.map(ModelMapper.java:230)
at org.modelmapper.ModelMapper.map(ModelMapper.java:185)
at mapper.MapperTest.mapWithCustomResolver(MapperTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:35)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:115)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:97)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103)
at com.sun.proxy.$Proxy0.invoke(Unknown Source)
at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150)
at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
Caused by: org.modelmapper.MappingException: ModelMapper mapping errors:

1) Failed to get value from mapper.Person.getLastName()

1 error
at org.modelmapper.internal.Errors.toMappingException(Errors.java:243)
at org.modelmapper.internal.PropertyInfoImpl$MethodAccessor.getValue(PropertyInfoImpl.java:107)
at org.modelmapper.internal.MappingEngineImpl.resolveSourceValue(MappingEngineImpl.java:195)
at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:162)
at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:140)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:101)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:70)
... 32 more
Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.modelmapper.internal.PropertyInfoImpl$MethodAccessor.getValue(PropertyInfoImpl.java:102)
... 37 more

Jonathan Halterman

unread,
Jun 6, 2013, 5:03:18 PM6/6/13
to model...@googlegroups.com
Hi Miha,

The firstName + lastName concatenation problem you're working on is similar to something that came up in a different thread just recently. Have a look at the solution there. I would love for things to "just work" the way you think they should, such as in your example, but currently ModelMapper is designed only to map a setter to a getter. Mapping a setter to the concatenated result of two getters is a different problem which is difficult since it's hard to know the context in which a getter is being called. I think it's a problem worth looking at though. Can you file this as an issue?

Thanks,
Jonathan

Miha Valencic

unread,
Jun 7, 2013, 12:42:04 PM6/7/13
to model...@googlegroups.com
Hi Johnatan!

I'll file an issue later - after I check the converter workaround;
perhaps it won't be necessary or it will at least give a better
insight.

1) I see that I need to use a different version. Is there a maven
repository for snapshots somewhere I can use to get the version that
implements getParent(), or should I just clone the repo and build it
myself?

2) Why is source field of converter null? This seems like a property to use

3) The syntax "using(converter).map().setDisplayName(null);" is
strange (the part setDisplayName(null)), but I suppose this is just
syntactic issue to work around the issue of using a converter?

Regards,
Miha.

ps: in the c# land, I was using AutoMapper (http://automapper.org/),
which deals with such things in a rather nice way, using lambdas.
Perhaps project like lambdaj would be interesting to see how they've
emulated lambdas for that?
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "modelmapper" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/modelmapper/065W6XumU4c/unsubscribe?hl=en.
> To unsubscribe from this group and all its topics, send an email to
> modelmapper...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Jonathan Halterman

unread,
Jun 7, 2013, 1:17:07 PM6/7/13
to model...@googlegroups.com


On Friday, June 7, 2013 9:42:04 AM UTC-7, Miha Valencic wrote:
Hi Johnatan!

I'll file an issue later - after I check the converter workaround;
perhaps it won't be necessary or it will at least give a better
insight.

1) I see that I need to use a different version. Is there a maven
repository for snapshots somewhere I can use to get the version that
implements getParent(), or should I just clone the repo and build it
myself?

There's no snapshot repo, so yes, just build it yourself. "mvn package" from the top level should work no problem.
 
 
2) Why is source field of converter null? This seems like a property to use

Where do you mean?
 

3) The syntax "using(converter).map().setDisplayName(null);" is
strange (the part setDisplayName(null)), but I suppose this is just
syntactic issue to work around the issue of using a converter?

Yes, it is strange. Our goal is to express that we want to use some converter when mapping values to some setter. The expression captures that pretty well, but we do still have to pass something into the setter in order to make the compiler happy. So we pass in a dummy null value.

Miha Valencic

unread,
Jun 7, 2013, 2:41:13 PM6/7/13
to model...@googlegroups.com
Hi Johnatan!

On Fri, Jun 7, 2013 at 7:17 PM, Jonathan Halterman <jhalt...@gmail.com> wrote:
>> 2) Why is source field of converter null? This seems like a property to
>> use
> Where do you mean?

I'm sorry, my bad. I declared converter inside "configure" method of
CustomMap (which extends PropertyMap<Person, PersonDTO>). And I saw in
the debugger, that I have a scope variable "source" available, which
is null (context.getSource() == null). Otherwise, source is a private
variable of MappingContext, which I suppose should be the source
object as well, but is also null. The doc comment for getSource says:
Returns the source object being mapped from.

I'll try with the latest version from github and see how that goes.

Regards,
Miha.
Message has been deleted

Jonathan Halterman

unread,
Jun 7, 2013, 2:54:10 PM6/7/13
to model...@googlegroups.com
Ah ok. In the examples we were talking about such as using(converter).map().setDisplayName(null), the source will be null when your Converter is called since we're not specifying a source to make available. So we have two options there, to make a source property available as the "source" when the Converter is called, or to make the entire source object available as the "source". Ex:

// Value of person.someGetter() becomes the source when converter is called
using(converter).map().setDisplayName(person.someGetter());
using(converter).map(person.someGetter()).setDisplayName(null);

// person becomes the source when converter is called
using(converter).map(person).setDisplayName(null);

Cheers,
Jonathan

On Thursday, June 6, 2013 3:57:38 AM UTC-7, Miha Valencic wrote:

Miha Valencic

unread,
Jun 7, 2013, 3:29:14 PM6/7/13
to model...@googlegroups.com
On Fri, Jun 7, 2013 at 8:54 PM, Jonathan Halterman <jhalt...@gmail.com> wrote:
> Ah ok. In the examples we were talking about such as
> using(converter).map().setDisplayName(null), the source will be null when
> your Converter is called since we're not specifying a source to make
> available. So we have two options there, to make a source property available
> as the "source" when the Converter is called, or to make the entire source
> object available as the "source". Ex:

Johnatan, indeed. I can confirm, that this works also in the released version:
public class CustomMap extends PropertyMap<Person, PersonDTO> {
@Override
protected void configure() {
Converter<Person, String> converter = new Converter<Person, String>() {
public String convert(MappingContext<Person, String> context) {
Person src = context.getSource();
return src.getFirstName() + " " + src.getLastName();
}
};

using(converter).map(source).setDisplayName(null);
}
}

So no need for context.getParent() in this case.

Also, for my final use case, where I need to make external lookups, I
can confirm, that creating a TypeMap with a reference, that is used to
lookup values when conversion takes place, also works. I tested this
with a very contrived example (seed could be the actual reference to
an external repository or whatever):

public class ExternalLookupMap extends PropertyMap<Person, PersonDTO> {
private int _seed;

public ExternalLookupMap(int seed) {
_seed = seed;
}

@Override
protected void configure() {
Converter<Person, String> c = new CustomConverter(_seed);
using(c).map(source).setDisplayName(null);
}

public static class CustomConverter implements Converter<Person, String>
{
private int seed;

public CustomConverter(int startingSeed)
{
seed = startingSeed;
}

public String convert(MappingContext<Person, String> context) {
return "Invocation " + (seed++);
}
}
}

Thank you for your support!

Regards,
Miha.
Reply all
Reply to author
Forward
0 new messages