4

What I am trying to do is to map a List of entities to a list of their String ids (more or less) using Dozer.

Obviously, it implies Custom Converter. My first idea was to make a converter from MyEntity to a String, and then say to Dozer something like "Map every object of this collection using this converter". But I couldn't figure out how to do so.

So my second idea was to make a converter form a list of entities to a list of string, directly. My problem on this idea is that I was strugling on something ridiculous which is to get the type of my list in the constructor, as below (which doesn't work at all):

public MyEntityListConverter() {
    super(List<MyEntity>.class, List<String>.class);
}

I don't know how to pass an instantiated list's class in a single row wihout declaring anything.

So if someone know either :

  • How to specify to dozer an object convertor to use in collection mapping
  • How to get instantiated list type
  • A third/better solution to try
tanou
  • 1,083
  • 2
  • 13
  • 33

3 Answers3

6

The way you tried is not possible due to generic types. And if it was, Dozer cannot detect types at runtime.

1st solution with List<>

Your converter :

public class MyEntityToStringConverter extends DozerConverter<MyEntity, String> {
    // TODO constructor + impl
}

Your mapping :

mapping(MyEntityA.class, MyEntityB.class)
.fields("myEntityList", "myStringList",
    hintA(MyEntity.class),
    hintB(String.class));

mapping(MyEntity.class, String.class)
.fields(this_(), this_(), customConverter(MyEntityToStringConverter.class));

2nd solution with list wrappers

You can try to create your custom classes extending a list impl.

public class MyEntityList extends ArrayList<MyEntity> {

}

public class MyStringList extends ArrayList<String> {

}

Change your field in the parent classes you want to map.

Your converter :

public class MyEntityToStringConverter extends DozerConverter<MyEntityList, MyStringList> {
    // TODO constructor + impl
}

Your mapping :

mapping(MyEntityA.class, MyEntityB.class)
.fields("myEntityList", "myStringList", customConverter(MyEntityToStringConverter.class));
Ludovic Guillaume
  • 3,237
  • 1
  • 24
  • 41
  • Thanks mate, I couldn't try it, I'm not on this project anymore, but I'm pretty sure it would have worked, so I accept the answer. – tanou Mar 30 '15 at 07:44
  • @LudovicGuillaume I tested half of **Solution 1** and I can be sure it's working. But I figured out a catch when using `this` for field mapping – I have to return the value via the passed in parameter rather than return object. But I couldn't change the value of `String` because it's always passed by value. Did I miss anything? – stephen Jan 14 '19 at 08:45
  • Actually, I don't understand what issue you have. The second mapping of solution 1 means that `MyEntity` can be mapped to `String` via `MyEntityToStringConverter`. Note that it's not `this` but `this_()`. The methods of the converter should be `MyEntity convert*(String)` and `String convert*(MyEntity)`. – Ludovic Guillaume Jan 21 '19 at 11:30
  • Can I ask what version of Dozer? Currently on 6.1.0 and 6.2.0, this solution gives me the value of toString from the entity. From looking into the Dozer code, the mapOrRecurseObject method in MappingProcessor uses the PrimitiveOrWrapperConverter if either of the source or destination are primitives. This converter assumes both arguments are primitives and adhere to basic mapping, i.e. no custom mapping are honored. – John Camerin Jan 27 '20 at 20:49
3

Another option would be

super((Class<List<MyEntity>>) (Class<?>) List.class,(Class<List<String>>) (Class<?>) List.class);
Song Choe
  • 71
  • 1
  • 10
  • First and foremost, this works so I I'm upvoting. But what's worth mentioning is there's a caveat to this solution – because generic type was erased during runtime, `DozerConverter` actually won't be able to tell `source` from `destination`, as both has the data type of `List.class`. Hence it'll always call `convertFrom()` method which makes the converter to work one way but not the other. – stephen Jan 14 '19 at 08:32
1

I very much inclined to @Ludovic solution, but there might be a catch as mentioned in my comment up there.

But a slight tweak works for me though - register the custom converter in "configuration" rather than field level. I'm using XML config but it should work with coding config:

<configuration>
  <custom-converters>
    <converter type="f.q.c.n.MyEntityToStringConverter">
      <class-a>java.lang.String</class-a>
      <class-b>f.q.c.n.MyEntity</class-b>
    </converter>
  </custom-converters>
</configuration>
stephen
  • 651
  • 7
  • 11