0

I have a model like this:

public class PersonDto {

    private CarDto car;

    public CarDto getCar() {
        return car;
    }

    public void setCar(CarDto car) {
        this.car = car;
    }

    public static class CarDto {

        private String model;

        private String color;

        public String getModel() {
            return model;
        }

        public void setModel(String model) {
            this.model = model;
        }

        public String getColor() {
            return color;
        }

        public void setColor(String color) {
            this.color = color;
        }
    }
}

And the VO is immutable:

public class PersonVo {

    private final CarDto car;

    public PersonVo(CarDto car) {
        super();
        this.car = car;
    }

    public CarDto getCar() {
        return car;
    }

    public static class CarDto {

        private final String model;

        private final String color;

        public CarDto(String model, String color) {
            super();
            this.model = model;
            this.color = color;
        }

        public String getModel() {
            return model;
        }

        public String getColor() {
            return color;
        }   
    }
}

Is there an easy way using ModelMapper Providers to convert this scenario from Dto to Vo?

I can't figure it out using one provider for PersonVo because when I'm creating the new object I don't know how to parse the CarDto...

public class PersonVoProvider implements Provider<PersonVo> {

    public PersonVo get(org.modelmapper.Provider.ProvisionRequest<PersonVo> request) {
        PersonDto source = PersonDto.class.cast(request.getSource());
        return new PersonVo(car); ????????????
    }
}
Nicolas Filotto
  • 43,537
  • 11
  • 94
  • 122
Gerard Ribas
  • 717
  • 1
  • 9
  • 17

2 Answers2

1

To convert one instance to other which is inmutable follow the next steps (In summary: 1. Create Provider, 2. Create Converter, 3. Add both to the ModelMapper configuration):

  1. First create one Provider using the AbstractProviderclass to avoid instantiate destination error (Failed to instantiate instance of destination com.example.pregunta.PersonVo. Ensure that com.example.pregunta.PersonVo has a non-private no-argument constructor). Your Providershould instantiate your inmutable class (In your case PersonVo), even the properties are final, with the value you want because this instantiate is just to avoid the instantiate error. For example:

    Provider<PersonVo> providerVo = new AbstractProvider<PersonVo>() {
    
        @Override
        protected PersonVo get() {
            PersonVo.CarDto carDto = new PersonVo.CarDto("", "");
            PersonVo personVo = new PersonVo(carDto);
    
            return personVo;
        }
    };
    
  2. Then you need to create a Converter with source PersonDtoand destination PersonVo. Converting one instance to other with your hand, as the next example:

    Converter<PersonDto, PersonVo> converterDtoToVo = new Converter<PersonDto, PersonVo>() {
        @Override
        public PersonVo convert(MappingContext<PersonDto, PersonVo> context) {
            PersonDto dto =  context.getSource();
    
            String color = dto.getCar().getColor();
            String model = dto.getCar().getModel();
            PersonVo.CarDto carVo = new PersonVo.CarDto(color, model);
            PersonVo vo =  new PersonVo(carVo);
    
            return vo;
        }
    };
    
  3. Finally,it is needed to add the provider and the converter to your ModelMapper instance configuration:

    ModelMapper mapper = new ModelMapper();
    mapper.getConfiguration().setProvider(providerVo);
    mapper.addConverter(converterDtoToVo);
    

Test

I've tried it with this example and works perfectly:

//Instances
PersonDto dto = new PersonDto();
PersonDto.CarDto carDto = new PersonDto.CarDto();
carDto.setColor("blue");
carDto.setModel("Picasso");
dto.setCar(carDto);

PersonVo vo =  mapper.map(dto, PersonVo.class);

System.out.println(vo);

Output:

PersonVo [car=CarDto [model=blue, color=Picasso]]

Poger
  • 1,897
  • 18
  • 16
Pau
  • 14,917
  • 14
  • 67
  • 94
  • 9
    Doesn't this defeat the purpose of ModelMapper? You're writing a lot of class-specific code that doesn't scale when the source or destination classes add or remove properties. This also doesn't work if your destination object validates the parameters in the constructor (which it should). – MikeWyatt Nov 13 '17 at 16:57
1

The example resolves the issue using lombok immutable objects and nested immutable with modelMapper

The only thing is we have to set the accessLevel to private and declare constructor as private to keep immutability, lombok works for us with annotations. See the example below:

First define modelMapper setting accesLevel to private

 ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration()
            .setFieldMatchingEnabled(true)
            .setFieldAccessLevel(Configuration.AccessLevel.PRIVATE);

and immutable objects:

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Value
@Builder
public class UserDto {

    private final Long id;

    private final String uid;

    private final String description;

    private final boolean enabled;

    @Singular
    private final Set<GroupUserDto> userGroups;
}

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class GroupUserDto {

    private Long id;

    private String name;

    private String description;

    private String comments;

    private boolean enabled;
}

Finally it works fine calling to modelMapper.map using the Builder parent class. All fields are mapped included nested immutable dto

UserDto dto = modelMapper.map(user,UserDto.UserDtoBuilder.class).build();
Sergio Gonzalez
  • 215
  • 1
  • 4
  • 11
  • Won't objectMapper complain about the private no-arg constructor? – TheCoder Apr 12 '18 at 07:31
  • ModelMapper passes values from a source bean to a target bean by getter and setter. The private constructors is to keep the object inmutable but it's true that the no-arg constructor is not necessary in this example. I change it, thanks. – Sergio Gonzalez Apr 12 '18 at 14:08
  • Doesn't work for me, it complains about missing "non-private" empty constructor – Tom Feiner Apr 19 '19 at 14:01
  • @tfeiner, Which version modelmapper are you using? this example uses 1.1.2 I've tested the example from scratch and it works for me. Pass me your email and i'ĺl send you the complete example. – Sergio Gonzalez Apr 23 '19 at 08:07