使用 JAXB 映射对象生成非 public 列表字段并使用 ModelMapper 缺少 setter

Mapping objects with JAXB generated non-public list fields and missing setters with 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>

我在 namespacea 和 namespaceb 中都有这个模式,所以 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;
    }
}

现在,如果我从包 namespacea 创建源 CityData 并要求 modelmapper 将其映射到 namespaceb 中的目标 CityData,则不会映射数据:

    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 找到 setter,因此没有映射它。我读到有人可以重新配置 Jaxb 来为列表属性生成 setters,但是 jaxb 对象生成不在我的项目中。

所以我想知道是否有一个很好的解决方案来使用 ModelMapper 进行映射,或者在这些情况下使用其他映射器库。

我创建了一个迷你项目:https://github.com/riskop/ModelMapperJaxb

我认为您只需启用 FieldMatching 并将字段的访问级别设置为匹配以处理缺失的 setter。检查此配置:

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

Javadoc:

setFieldAccessEnabled

Sets whether field matching should be enabled. When true, mapping may take place between accessible fields. Default is false.

setFieldAccessLevel

Indicates that fields should be eligible for matching at the given accessLevel.
Note: Field access is only used when field matching is enabled.

在阅读 pirho 的回答之前,我对 ModelMapper.Converter 工具的笨拙解决方法有一个粗略的想法。我认为 pirho 的回答更好(已接受),但为了记录,下面是 Converter 解决方法。这基本上是手动定义没有 setter:

的子结构的转换
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());