Map a list of object to another list using Dozer's custom converters
Asked Answered
B

3

5

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
Breastbeating answered 13/3, 2015 at 13:40 Comment(0)
I
7

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));
Incantation answered 26/3, 2015 at 16:36 Comment(4)
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.Breastbeating
@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?Homogenetic
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).Incantation
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.Garcon
F
3

Another option would be

super((Class<List<MyEntity>>) (Class<?>) List.class,(Class<List<String>>) (Class<?>) List.class);
Fung answered 1/11, 2017 at 21:16 Comment(1)
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.Homogenetic
H
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>
Homogenetic answered 14/1, 2019 at 10:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.