Сопоставление объектов с сгенерированными JAXB закрытыми полями списка и отсутствующими сеттерами с помощью ModelMapper

У меня есть структуры данных, сгенерированные jaxb. Части структур в основном идентичны, но они находятся в разных пространствах имен, и поэтому сгенерированные типы Java различны.

Мне нужно передать данные между этими структурами. В проекте для сопоставления используется ModelMapper, поэтому я должен использовать его.

Моя проблема в том, что ModelMapper не может отображать списки, сгенерированные для элементов «maxOccurs = unbounded».

Допустим, у меня есть следующая схема:

<xs:complexType name="CityData">
    <xs:sequence>
        <xs:element name="districtData" type="DistrictData" maxOccurs="unbounded"/>
    </xs:sequence>
</xs:complexType>

<xs:complexType name="DistrictData">
    <xs:sequence>
        <xs:element name="population" type="xs:int" nillable="false" minOccurs="1" maxOccurs="1"/>
    </xs:sequence>
</xs:complexType>

У меня есть эта схема в пространстве имен a и в пространстве имен b, поэтому Jaxb генерирует следующие типы в пространстве имен пакетов a и в пространстве имен пакетов b:

public class CityData {
    @XmlElement(required = true)
    protected List<DistrictData> districtData;
    //... jaxb explanation why there's no setter
    public List<DistrictData> getDistrictData() {
        if (districtData == null) {
            districtData = new ArrayList<DistrictData>();
        }
        return this.districtData;
    }
}

public class DistrictData {
    protected int population;
    public int getPopulation() {
        return population;
    }
    public void setPopulation(int value) {
        this.population = value;
    }
}

Теперь, если я создам источник CityData из пространства имен пакета и попрошу modelmapper сопоставить его с местом назначения CityData в пространстве имен b, тогда данные не будут сопоставлены:

    CityData cityData = new CityData();
    DistrictData districtData = new DistrictData();
    districtData.setPopulation(1234);
    cityData.getDistrictData().add(districtData);

    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

    namespaceb.CityData dest = modelMapper.map(cityData, namespaceb.CityData.class);
    System.out.println("dest.districtData: " + dest.getDistrictData());

результат:

dest.districtData: []

Другими словами, DistrictData не копируется в место назначения.

Я понимаю, что ModelMapper не находит сеттер для DistrictData и поэтому не сопоставляет его. Я читал, что можно перенастроить Jaxb для создания сеттеров для свойств списка, но генерация объекта jaxb не входит в мои руки в проекте.

Поэтому я хотел бы узнать, есть ли хорошее решение для сопоставления с ModelMapper или, возможно, с другой библиотекой сопоставления в этих случаях.

Я создал мини-проект: https://github.com/riskop/ModelMapperJaxb


person riskop    schedule 24.11.2020    source источник


Ответы (2)


Я думаю, что вам просто нужно включить FieldMatching и установить уровень доступа к полям для обработки отсутствующего установщика. Проверьте эту конфигурацию:

modelMapper.getConfiguration()
    .setMatchingStrategy(MatchingStrategies.STRICT)
    .setFieldMatchingEnabled(true)
    .setFieldAccessLevel(AccessLevel.PROTECTED);

Javadoc:

setFieldAccessEnabled

Устанавливает, должно ли быть включено сопоставление полей. При значении true может иметь место сопоставление между доступными полями. Значение по умолчанию — ложь.

setFieldAccessLevel

Указывает, что поля должны иметь право на сопоставление на данном уровне доступа.
Примечание. Доступ к полю используется только при включенном сопоставлении полей.

person pirho    schedule 24.11.2020

У меня было приблизительное представление о неуклюжем обходном пути с помощью средства ModelMapper.Converter, прежде чем читать ответ Пирхо. Я думаю, что ответ pirho лучше (принят), но для протокола ниже приведен обходной путь конвертера. Это в основном ручное определение преобразования для подструктур, где нет установщика:

CountryData countryData = new CountryData();
CityData cityData = new CityData();
DistrictData districtData = new DistrictData();
districtData.setPopulation(1234);
cityData.getDistrictData().add(districtData);
countryData.getCityData().add(cityData);

ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

modelMapper.addConverter(new Converter<CountryData, namespaceb.CountryData>() {
    @Override
    public namespaceb.CountryData convert(MappingContext<CountryData, namespaceb.CountryData> context) {
        namespaceb.CountryData result = new namespaceb.CountryData();
        if(context.getSource() != null) {
            for(CityData cityData : context.getSource().getCityData()) {
                namespaceb.CityData mapped = modelMapper.map(cityData, namespaceb.CityData.class);
                result.getCityData().add(mapped);
            }
        }
        return result;
    }
});

modelMapper.addConverter(new Converter<CityData, namespaceb.CityData>() {
    @Override
    public namespaceb.CityData convert(MappingContext<CityData, namespaceb.CityData> context) {
        namespaceb.CityData result = new namespaceb.CityData();
        if(context.getSource() != null) {
            for(DistrictData districtData : context.getSource().getDistrictData()) {
                namespaceb.DistrictData mapped = modelMapper.map(districtData, namespaceb.DistrictData.class);
                result.getDistrictData().add(mapped);
            }
        }
        return result;
    }
});

namespaceb.CountryData destCountryData = modelMapper.map(countryData, namespaceb.CountryData.class);
assertEquals(1, destCountryData.getCityData().size());
namespaceb.CityData destCityData = destCountryData.getCityData().get(0);
assertEquals(1, destCityData.getDistrictData().size());
namespaceb.DistrictData destDistrictData = destCityData.getDistrictData().get(0);
assertEquals(1234, destDistrictData.getPopulation());
person riskop    schedule 25.11.2020