Agustin Miani
Agustin Miani

Reputation: 31

Failed to instantiate instance of destination [subclassDTO]. Ensure that [subclassDTO] has a non-private no-argument constructor

Im working on a Springboot App exposing some endpoints that do not return the original Entities but they return their DTO. To map all the entities i am working using "org.modelmapper" version: 0.7.4 and project lombok to avoid implement getters, setters and some another utilities.

Everything worked perfectly until I had to map a list of subclasses on a entity. The source entity is like this (i removed some properties and hibernate annotations because they are not necessary in the example):

package com.pfi.repository.entity.sport;

import com.pfi.repository.entity.address.Address;
import lombok.*;

import java.util.List;

@Getter
@Setter
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class SportPlace {

    private Long id;
    private List<AbstractSportField> sportFields;
}

The destination DTO is:

package dto.sport;

import lombok.*;

import java.util.List;

@Getter
@NoArgsConstructor
@Setter
public class SportPlaceDTO {

    private Long id;
    private List<AbstractSportFieldDTO> sportFields;
}

Both AbstractSportFieldDTO and AbstractSportField are abstract classes that with two possible subclasses:

package dto.sport;

import dto.reserve.AbstractReserveDTO;

import java.util.List;

public abstract class AbstractSportFieldDTO {

    protected Long id;
    protected List<AbstractReserveDTO> reserves;
    public AbstractSportFieldDTO() {
    }

    public AbstractSportFieldDTO(Long id, List<AbstractReserveDTO> reserves) {
        this.id = id;
        this.reserves = reserves;
    }

    public Long getId() {
        return id;
    }

    public AbstractSportFieldDTO setId(Long id) {
        this.id = id;
        return this;
    }


    public List<AbstractReserveDTO> getReserves() {
        return reserves;
    }

    public AbstractSportFieldDTO setReserves(List<AbstractReserveDTO> reserves) {
        this.reserves = reserves;
        return this;
    }
}

The first DTO subclass is:

package dto.sport;


import dto.reserve.AbstractReserveDTO;

import java.util.List;

public class ComboSportFieldDTO extends AbstractSportFieldDTO {

    private List<SportFieldDTO> sportFields;

    public ComboSportFieldDTO(List<SportFieldDTO> sportFields) {
        this.sportFields = sportFields;
    }

    public ComboSportFieldDTO(Long id, List<AbstractReserveDTO> reserves, List<SportFieldDTO> sportFields) {
        super(id, reserves);
        this.sportFields = sportFields;
    }

    public ComboSportFieldDTO() {
        super();
    }

    public List<SportFieldDTO> getSportFields() {
        return sportFields;
    }

    public void setSportFields(List<SportFieldDTO> sportFields) {
        this.sportFields = sportFields;
    }
}

The second DTO subclass is:

package dto.sport;

import dto.reserve.AbstractReserveDTO;

import java.util.List;

public class SportFieldDTO extends AbstractSportFieldDTO {

    private Boolean joineable;

    public SportFieldDTO(Long id, List<AbstractReserveDTO> reserves, Boolean joineable) {
        super(id, reserves);
        this.joineable = joineable;
    }

    public SportFieldDTO() {
        super();
    }

    public Boolean getJoineable() {
        return joineable;
    }

    public void setJoineable(Boolean joineable) {
        this.joineable = joineable;
    }
}

Then the Entities are:

package com.pfi.repository.entity.sport;

import com.pfi.repository.entity.reserve.AbstractReserve;
import java.util.List;

public abstract class AbstractSportField {

    protected Long id;
    protected List<AbstractReserve> reserves;

    public AbstractSportField(Long id, List<AbstractReserve> reserves) {
        this.id = id;
        this.reserves = reserves;
    }

    public AbstractSportField() {

    }

    public Long getId() {
        return id;
    }

    public AbstractSportField setId(Long id) {
        this.id = id;
        return this;
    }

    public List<AbstractReserve> getReserves() {
        return reserves;
    }

    public AbstractSportField setReserves(List<AbstractReserve> reserves) {
        this.reserves = reserves;
        return this;
    }

}

The first subclass is:

package com.pfi.repository.entity.sport;

import com.pfi.repository.entity.reserve.AbstractReserve;

import java.util.List;

public class ComboSportField extends AbstractSportField {

    private List<SportField> sportFields;

    public ComboSportField(Long id, List<AbstractReserve> reserves, List<SportField> sportFields) {
        super(id, reserves);
        this.sportFields = sportFields;
    }

    public ComboSportField(){
        super();
    }

    public List<SportField> getSportFields() {
        return sportFields;
    }

    public ComboSportField setSportFields(List<SportField> sportFields) {
        this.sportFields = sportFields;
        return this;
    }
}

And the last subclass is:

package com.pfi.repository.entity.sport;

import com.pfi.repository.entity.reserve.AbstractReserve;

import javax.persistence.Entity;
import java.util.List;

public class SportField extends AbstractSportField {

    private Boolean joineable;

    public SportField(Long id, List<AbstractReserve> reserves, Boolean joineable) {
        super(id, reserves);
        this.joineable = joineable;
    }

    public SportField(){
        super();
    }

    public Boolean getJoineable() {
        return joineable;
    }

    public SportField setJoineable(Boolean joineable) {
        this.joineable = joineable;
        return this;
    }
}

When i try to map an instance of SportPlace to SportPlaceDTO like this:

modelMapper.map(sportPlace, SportPlaceDTO.class)

That de sportPlace contains the given 6 Sportfields that are instances of subclasses. The model mapper throws (on the 6 elements of the list):

Failed to instantiate instance of destination dto.sport.AbstractSportFieldDTO. Ensure that dto.sport.AbstractSportFieldDTO has a non-private no-argument constructor.

Reading about how can i map subclasses on modelmapper i found setting TypeMap, that can set a specific mapping on a class like this:

modelMapper.createTypeMap(ComboSportField.class, ComboSportFieldDTO.class);

For that reason i created a builder to config the model mapper like this

package com.pfi.repository.builder;

import com.pfi.repository.entity.*;;
import dto.reserve.*;
import dto.sport.*;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.stereotype.Service;

@Service
public class MapperBuilder {

    public ModelMapper buildModelMapper() {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        mapEntitiesToDTO(modelMapper);
        mapDTOToEntities(modelMapper);
        return modelMapper;
    }

    private void mapEntitiesToDTO(final ModelMapper modelMapper){
        modelMapper.createTypeMap(ComboSportField.class, ComboSportFieldDTO.class);
        modelMapper.createTypeMap(SportField.class, SportFieldDTO.class);
        modelMapper.createTypeMap(Reserve.class, ReserveDTO.class);
        modelMapper.createTypeMap(AppReserve.class, AppReserveDTO.class);

    }
    private void mapDTOToEntities(final ModelMapper modelMapper){
        modelMapper.createTypeMap(ComboSportFieldDTO.class, ComboSportField.class);
        modelMapper.createTypeMap(SportFieldDTO.class, SportField.class);
        modelMapper.createTypeMap(ReserveDTO.class, Reserve.class);
        modelMapper.createTypeMap(AppReserveDTO.class, AppReserve.class);
    }
}

And i use the builder when i create the controller like this:

@Autowired
    protected MapperBuilder mapperBuilder;

    BaseController(){
        this.getLogger();
    }

    @PostConstruct
    public void buildMapper(){
        modelMapper = mapperBuilder.buildModelMapper();
    }

I don't know what else I can do to be able map the subclasses.

Does anyone know how to solve this issue?

Thanks!

Upvotes: 2

Views: 8216

Answers (2)

Luca Tampellini
Luca Tampellini

Reputation: 1849

Actually i have just experienced the same issue.

In my case the class for which modelmapper was complaining of a lack of public constructor was actually referenced as property inside another DTO class, mapped elsewhere, for which the public constructor was effectively not explicitly declared.

I have added such public constructor and this resolved the issue for me.

Upvotes: 1

Agustin Miani
Agustin Miani

Reputation: 31

I finally solved this issue with the help of some friends and another questions on this site.

My solution

Another Dev posted an issue similar to mine Here.

This post recomends the use of typeMap (but not as I used it) with a "converter" declaring. When a Subclass tries to map to AbstractClassDTO it must instantiate a subclassDTO.

Like this:

public ModelMapper buildModelMapper() {
    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
    mapEntitiesToDTO(modelMapper);
    mapDTOToEntities(modelMapper);
    return modelMapper;
}

private void mapEntitiesToDTO(final ModelMapper modelMapper) {
    modelMapper.createTypeMap(ComboSportField.class, AbstractSportFieldDTO.class)
        .setConverter(converterWithDestinationSupplier(ComboSportFieldDTO::new));
    modelMapper.createTypeMap(SportField.class, AbstractSportFieldDTO.class)
        .setConverter(converterWithDestinationSupplier(SportFieldDTO::new));
}

private void mapDTOToEntities(final ModelMapper modelMapper) {
    modelMapper.createTypeMap(ComboSportFieldDTO.class, AbstractSportField.class)
        .setConverter(converterWithDestinationSupplier(ComboSportField::new));
    modelMapper.createTypeMap(SportFieldDTO.class, AbstractSportField.class)
        .setConverter(converterWithDestinationSupplier(SportField::new));
}

private < S, D > Converter < S, D > converterWithDestinationSupplier(Supplier << ? extends D > supplier) {
    return ctx - > ctx.getMappingEngine().map(ctx.create(ctx.getSource(), supplier.get()));
}

When I tested this configuration it still didn't work. Then I found that the same dev that posted the last question posted an issue on modelmapper GitHub repo here. And the last comment reports that typeMap with inheritance had been released on ModelMapper 1.0.0 and I found that I was using the version 0.7.4.

So I updated the version to the current (2.3.5) and this started to work. Link to maven repo.

I hope that my description can help someone with the same issue. Thanks!

Upvotes: 1

Related Questions