3

Having something like:

@Getter @Setter
public static class Entity {
    private int hash;
    private LocalDateTime createdTime;
}

and

@Getter @Setter
public static class DTO {
    private String hash;
    private String createdTime;
}

I need birectional mapping so I should be able to map Entity -> DTO -> Entity. In this example the property type happens to be LocalDateTime but could be any type that needs parsing from String or so (just to say that I am not after better way to map LocalDateTime but in general).

There are no problems in mapping. I create TypeMap, add Converter and for LocalDateTime a Provider also since it does note have public default constructor. Something like here.

If I had in my DTO also LocalDateTime createdTime(or String createdTime in my Entity) then ModelMapper.validate() would be happy. But I do not have and I need to create all the converting stuff.

All this leads to ModelMapper.validate() to complain:

Unmapped destination properties found in TypeMap[DTO -> Entity]:  
    org.example.test.modelmapper.validation.TestIt$Entity.setCreatedTime()

The code I currently use for validating mapping for LocalDateTime case is:

ModelMapper mm = new ModelMapper();
mm.createTypeMap(Entity.class, DTO.class);
mm.createTypeMap(DTO.class, Entity.class);
mm.createTypeMap(String.class, LocalDateTime.class)
        .setPropertyProvider(localDateTimeProvider);
mm.addConverter(toStringDate);
mm.validate();

(so I am not doing any actual mapping but validating the mapping)

with

Provider<LocalDateTime> localDateTimeProvider =
        new AbstractProvider<LocalDateTime>() {
    @Override
    public LocalDateTime get() {
        return LocalDateTime.now();
    }
};

and

Converter<String, LocalDateTime> toStringDate = new AbstractConverter<>() {
    @Override
    protected LocalDateTime convert(String source) {
        return LocalDateTime.parse(source);
    }
};

Ask for more details/code. I'll update question as needed

pirho
  • 11,565
  • 12
  • 43
  • 70

2 Answers2

1

The setPropertyProvider method allows to specify a Provider to be used for providing instances of mapped properties within a TypeMap.

So when you write:

mm.createTypeMap(String.class, LocalDateTime.class)
        .setPropertyProvider(localDateTimeProvider);

It does not fit the case because we are not using this provider in the mapping of a property of the String type to a property of a LocalDateTime type. It should rather be moved above to be associated with the DTO -> Entity TypeMap (The error message is by the way a good hint about that). So it should rather be.

mm.createTypeMap(DTO.class, Entity.class)
                .setPropertyProvider(localDateTimeProvider);

Which makes perfect sense because we are using the provider to provide instance for the mapping of a String property of the DTO (String createdTime;) to a LocalDateTime property of the Entity (LocalDateTime createdTime;).

On the other hand the converter should be added to the ModelMapper before the corresponding provider.

Also leaving in mm.createTypeMap(String.class, LocalDateTime.class), my compiler complains that a similar typemap already exist and there is no need to create a new one. So with that I can discard it.

With these two changes, my bean looks like:

@Bean
ModelMapper demoModelMapper() {

   Provider<LocalDateTime> localDateTimeProvider =
   new AbstractProvider<LocalDateTime>() {
      @Override
      public LocalDateTime get() {
         return LocalDateTime.now();
      }
   };

   Converter<String, LocalDateTime> toStringDate = new AbstractConverter<String, 
   LocalDateTime>() {
      @Override
      protected LocalDateTime convert(String source) {
         return LocalDateTime.parse(source);
      }
   };

   ModelMapper mm = new ModelMapper();


   mm.createTypeMap(Entity.class, DTO.class);
   mm.addConverter(toStringDate);
   mm.createTypeMap(DTO.class, Entity.class)
     .setPropertyProvider(localDateTimeProvider);
   mm.validate();

   return mm;
}

Notice that I am calling validate() before returning the bean. This works for me. Please test and see on your side.

alainlompo
  • 4,414
  • 4
  • 32
  • 41
  • Appreciate your help. So far I have not succeeded with your solution but I investigate it further. Only difference is that I am not using @Bean just vanilla Java. Another thing is that it might become cumbersome if I need to add provider every time to every type mapping having property needing conversion. Have any suggestions for that? – pirho Jan 03 '20 at 16:44
  • I am a bit more near the solution. It just requires to leave out the `propertyProvider` part. – pirho Jan 03 '20 at 17:48
  • @pirho my example can easily be adapted to Vanilla Java. What problem are you currently having? – alainlompo Jan 03 '20 at 17:53
  • Just the same error that was in the question. But it seems that if I leave out `setPropertyProvider` part and add only converter there is no prob. But I need to test it more. It helped to add the converter to mapper before creating the type map. And yes, I adapted it to vanilla Java, was just wondering if there was some Spring(?) magic behind the bean. – pirho Jan 03 '20 at 17:56
  • No, no Spring magic, just @Bean to provide the method output as a bean. Let me know how it goes. – alainlompo Jan 03 '20 at 18:01
0

As in answer from alainlompo I had to move the adding of converter before the creation of type map.

But I also had to remove the provider part because it seemed to cause all string fields to be mapped as LocalDateTime so I got errors like:

org.modelmapper.MappingException: ModelMapper mapping errors:
1) The provided destination instance 2020-01-05T17:28:22.088694 is not of the required type int.

Above I think means that ModelMapper tried to populate field hash with a string representing LocalDateTime.

It seems that the provider is not needed at all. So my final code with just a converter added:

ModelMapper mm = new ModelMapper();
mm.createTypeMap(Entity.class, DTO.class);
mm.addConverter(toStringDate);
mm.createTypeMap(DTO.class, Entity.class);//.setPropertyProvider(localDateTimeProvider);
mm.validate();

This actually means that I asked a bit wrong question claiming that I need to use the provider

pirho
  • 11,565
  • 12
  • 43
  • 70