ModelMapper: matches multiple source property hierarchies
Asked Answered
D

6

33

I cannot resolve modelMapper error. Do you have any ideas where is the issue?

NB: In view java.sql.Time doesn't have non-argument constructor I didn't find the better way than to write converter

org.modelmapper.ConfigurationException: ModelMapper configuration errors:

1) The destination property 
biz.models.CarWash.setSecondShift()/java.util.Date.setTime() matches 
multiple source property hierarchies:

biz.dto.CarWashDTO.getFirstShift()/java.time.LocalTime.getSecond()
biz.dto.CarWashDTO.getSecondShift()/java.time.LocalTime.getSecond()

The error was made by this code

@SpringBootTest
@RunWith(SpringRunner.class)
public class CarWashDTO2CarWash {

@Autowired
protected ModelMapper modelMapper;

@Test
public void testCarWashDTO2CarWash_allFiledShouldBeConverted(){
    CarWashDTO dto = CarWashDTO.builder()
            .name("SomeName")
            .address("SomeAddress")
            .boxCount(2)
            .firstShift(LocalTime.of(9, 0))
            .secondShift(LocalTime.of(20, 0))
            .phoneNumber("5700876")
            .build();

    modelMapper.addConverter((Converter<CarWashDTO, CarWash>) mappingContext -> {
        CarWashDTO source = mappingContext.getSource();
        CarWash destination = mappingContext.getDestination();
        destination.setId(source.getId());
        destination.setFirstShift(source.getFirstShift() == null ? null : Time.valueOf(source.getFirstShift()));
        destination.setSecondShift(source.getSecondShift() == null ? null : Time.valueOf(source.getSecondShift()));
        destination.setEnable(true);
        destination.setAddress(source.getAddress());
        destination.setBoxCount(source.getBoxCount());
        destination.setName(source.getName());
        destination.setDateOfCreation(source.getDateOfCreation());
        return destination;
    });

    final CarWash entity = modelMapper.map(dto, CarWash.class);
    assertNotNull(entity);
    assertEquals(2, entity.getBoxCount().intValue());
    assertEquals("SomeAddress", entity.getAddress());
    assertEquals("SomeName", entity.getName());
}

}

The modelmapper bean is built by the next configuration

@Bean
public ModelMapper modelMapper(){
    return new ModelMapper();
}

Dto:

public class CarWashDTO {
private Long id;
private String name;
private String address;
private String phoneNumber;
private Integer boxCount;
private LocalTime firstShift;
private LocalTime secondShift;
private LocalDateTime dateOfCreation;
}

Entity (firstShift and secondShift have java.sql.Time type):

public class CarWash {
private Long id;
private String name;
private String address;
private String phoneNumber;
private Integer boxCount;
private Time firstShift;
private Time secondShift;
private LocalDateTime dateOfCreation;
private Boolean enable;
private Owner owner;
}
Declamation answered 14/4, 2018 at 13:4 Comment(0)
L
55

try modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT)

Liquefacient answered 6/12, 2019 at 14:35 Comment(3)
This is the best answer. I have still seen conflicts with: modelMapper.getConfiguration().setAmbiguityIgnored(true);Iconoclasm
modelMapper.getConfiguration().setAmbiguityIgnored(true) skips the value setter. But modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT) sets the best matched value.This works for me.Busoni
STRICT strategy is not going to help, If you do so there will be no exception but the value that will set are null or zero. The exact solution is setting the Ambiguity in the configuration of model mapper which checks for duplicate matches.Decoupage
A
26

This resolved my problem: modelMapper.getConfiguration().setAmbiguityIgnored(true);

Abbottson answered 31/10, 2018 at 16:23 Comment(3)
This configuration only makes ModelMapper skip the field. In this case the CarWashDTO.id field will be null after mapping.Nippur
This worked for me first I did 1) mapper.getConfiguration() .setAmbiguityIgnored(true); then configured the typeMap 2) mapper.createTypeMap(...).addMappings() after that set 3) mapper.getConfiguration() .setAmbiguityIgnored(false) and finally 4) mapper.validate()Robin
This didn't work for me, returns null. The below answer worked however.Kluge
N
5

You need to customize ModelMapper configuration during Bean initialization with the help of a PropertyMap: http://modelmapper.org/user-manual/property-mapping/

@Bean
public ModelMapper modelMapper(){
    ModelMapper mm = new ModelMapper();

    PropertyMap<CarWashDTO, CarWash> propertyMap = new PropertyMap<CarWashDTO, CarWash> (){
        protected void configure() {
            map(source.getId()).setId(null);
        }
    }

    mm.addMappings(propertyMap);
    return mm;
}
Nippur answered 4/12, 2018 at 11:26 Comment(0)
B
1

I am not sure how it was with ModelMapper when question was asked but using converter should be straightforward. Instead of implementing a converter for the whole class implement it to the types that actually need conversion. So like:

public static Converter<LocalTime, Time> timeConverter = new AbstractConverter<>() {
    @Override
    protected Time convert(LocalTime source) {
        return null == source ? null : Time.valueOf(source);
    }
}; 

Then it is just to:

mm.addConverter(timeConverter);

Guess if using Spring or EJB you know howto add this into your configuration.

Boudreaux answered 3/11, 2020 at 18:11 Comment(0)
H
0

This resolved my problem:

modelMapper.getConfiguration().setAmbiguityIgnored(true);
Hear answered 7/5, 2023 at 11:18 Comment(0)
E
0

If the suggested solutions to use MatchingStrategies.STRICT or setAmbiguityIgnored(true) are not an option (for example when there is only a single ModelMapper instance, which already performs many different mappings, so potential side effects could be an issue), there's another workaround.

Instead of creating the TypeMap with createTypeMap (or typeMap, which works very subtly differently but is basically the same), use emptyTypeMap. This completely disables the construction of all the implicit mappings, which are the cause of those ambiguity problems in the case of similarly named attributes. With this basis, you can (and must) then apply all mappings explicitly. I guess this could be likened to enabling STRICT matching for this specific TypeMap only.

So for the car wash example and using the fluent interface, it would be something like this:

modelMapper.emptyTypeMap(CarWashDTO.class, CarWash.class)
           .addMapping(CarWashDTO::getFirstShift, CarWash::setFirstShift) 
           .addMapping(CarWashDTO::getSecondShift, CarWash::setSecondShift) 
           [...]

I haven't tried it myself yet, but it might even be possible to only specify the mappings explicitly for the conflicting attributes and use the implicit mapping again for the rest. This would be achieved by terminating the chain with implicitMappings().

Evonevonne answered 29/12, 2023 at 13:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.