You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ModelMapper映射JSON POJO到XML POJO失败问题求助

问题描述

尝试使用ModelMapper将JSON POJO映射到XML POJO时失败:源JSON POJO(jsonSource)的Employees列表长度为2,但目标XML POJO(xml)的Employees列表长度为0,数据未成功迁移。

测试代码片段

@Test
public void givenJsonSource_whenMapToXmlDest_thenFieldsMapCorrectly() throws Exception {

    Employees jsonSource = new Employees();
    List<Employee> employees = new ArrayList<>();

    Employee employee1 = new Employee();
    employee1.setName("thename");
    employee1.setGender("M");
    employee1.setAge(30);
    employees.add(employee1);

    Employee employee2 = new Employee();
    employee2.setName("thename2");
    employee2.setGender("M2");
    employee2.setAge(32);
    employees.add(employee2);

    jsonSource.setEmployees(employees);

    aaa.bbb.ccc.jar.generated.Employees xml = mapper.map(jsonSource, aaa.bbb.ccc.jar.generated.Employees.class);

    System.out.println("jsonSource.getEmployees().size()=>" + jsonSource.getEmployees().size());  // <<< 2
    System.out.println("xmlDest.getEmployee().size()=>" + xml.getEmployees().size());  // <<< 0

    // verify fields
    assertEquals(xml.getEmployees().get(0).getName(), jsonSource.getEmployees().get(0).getName());
}

JSON POJO类

Employee类

package aaa.bbb.ccc.jar;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Employee {

    @JsonProperty("name") 
    String name;
    @JsonProperty("gender")     
    String gender;
    @JsonProperty("age")     
    Integer age;
    
    public Employee() {
    }
    public Employee(String name, String gender, Integer age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Employee{" + "name=" + name + ", gender=" + gender + ", age=" + age + '}';
    }

}

Employees类

package aaa.bbb.ccc.jar;

import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;

@JsonRootName(value = "Employees")
public class Employees {
    
    @JsonProperty(value = "Employees")
    protected List<Employee> employees;

    public Employees() {
    }

    public List<Employee> getEmployees() {
        if (employees == null) {
            employees = new ArrayList<>();
        }
        return this.employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

    @Override
    public String toString() {
        return "Employees{" + "employees=" + employees + '}';
    }
    
}

生成的XML POJO类

Employee类

//
// This file was generated by the Eclipse Implementation of JAXB, v3.0.0 
// See https://eclipse-ee4j.github.io/jaxb-ri 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2024.04.12 at 08:27:40 AM EDT 
//

package aaa.bbb.ccc.jar.generated;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlType;

/**
 * <p>Java class for Employee complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * &lt;complexType name="Employee"&gt;
 *   &lt;complexContent&gt;
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
 *       &lt;all&gt;
 *         &lt;element name="Name" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/&gt;
 *         &lt;element name="Gender" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/&gt;
 *         &lt;element name="Age" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/&gt;
 *       &lt;/all&gt;
 *     &lt;/restriction&gt;
 *   &lt;/complexContent&gt;
 * &lt;/complexType&gt;
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Employee", propOrder = {

})
public class Employee {

    @XmlElement(name = "Name")
    protected String name;
    @XmlElement(name = "Gender")
    protected String gender;
    @XmlElement(name = "Age")
    protected Integer age;

    /**
     * Gets the value of the name property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getName() {
        return name;
    }

    /**
     * Sets the value of the name property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setName(String value) {
        this.name = value;
    }

    /**
     * Gets the value of the gender property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getGender() {
        return gender;
    }

    /**
     * Sets the value of the gender property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setGender(String value) {
        this.gender = value;
    }

    /**
     * Gets the value of the age property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getAge() {
        return age;
    }

    /**
     * Sets the value of the age property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setAge(Integer value) {
        this.age = value;
    }

}

Employees类

//
// This file was generated by the Eclipse Implementation of JAXB, v3.0.0 
// See https://eclipse-ee4j.github.io/jaxb-ri 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2024.04.12 at 08:27:40 AM EDT 
//

package aaa.bbb.ccc.jar.generated;

import java.util.ArrayList;
import java.util.List;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;

/**
 * <p>Java class for anonymous complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * &lt;complexType&gt;
 *   &lt;complexContent&gt;
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
 *       &lt;sequence&gt;
 *         &lt;element name="Employees" type="{http://www.aaa.bbb.ccc/jar/mmdemo}Employee" maxOccurs="unbounded" minOccurs="0"/&gt;
 *       &lt;/sequence&gt;
 *     &lt;/restriction&gt;
 *   &lt;/complexContent&gt;
 * &lt;/complexType&gt;
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "employees"
})
@XmlRootElement(name = "Employees")
public class Employees {

    @XmlElement(name = "Employees")
    protected List<Employee> employees;

    /**
     * Gets the value of the employees property.
     * 
     * <p>
     * This accessor method returns a reference to the live list,
     * not a snapshot. Therefore any modification you make to the
     * returned list will be present inside the Jakarta XML Binding object.
     * This is why there is not a <CODE>set</CODE> method for the employees property.
     * 
     * <p>
     * For example, to add a new item, do as follows:
     * <pre>
     *    getEmployees().add(newItem);
     * </pre>
     * 
     * 
     * <p>
     * Objects of the following type(s) are allowed in the list
     * {@link Employee }
     * 
     * 
     */
    public List<Employee> getEmployees() {
        if (employees == null) {
            employees = new ArrayList<Employee>();
        }
        return this.employees;
    }

}

XSD文件

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema 
    targetNamespace="http://www.aaa.bbb.ccc/jar/mmdemo"
    attributeFormDefault="unqualified"
    elementFormDefault="qualified"
    xmlns:es="http://www.aaa.bbb.ccc/jar/mmdemo"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
            
    <xs:complexType name="Employee">
        <xs:all>
            
            <xs:element name="Name" type="xs:string" minOccurs="0" />
            <xs:element name="Gender" type="xs:string" minOccurs="0" />
            <xs:element name="Age" type="xs:int" minOccurs="0" />
                                                            
        </xs:all>
    </xs:complexType>

    <xs:element name="Employees">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Employees" type="es:Employee" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

</xs:schema>
问题原因分析
  1. 目标类无集合setter方法:生成的XML Employees类只有getEmployees()方法,没有setEmployees()方法,ModelMapper默认会尝试通过setter方法注入集合数据,找不到对应方法时无法完成映射。
  2. 跨包Employee类映射:源JSON的Employee类(aaa.bbb.ccc.jar.Employee)和目标XML的Employee类(aaa.bbb.ccc.jar.generated.Employee)属于不同包,ModelMapper需要明确配置才能识别这两个类的字段映射关系。
  3. ModelMapper默认配置限制:默认配置下,ModelMapper可能无法自动处理“获取live集合并添加元素”的场景,需要调整匹配策略或添加自定义转换器。
解决方案

方案1:配置ModelMapper以支持无setter的集合映射

通过调整ModelMapper的配置,使其能够直接访问目标类的集合字段,或者通过getter获取集合后添加元素:

ModelMapper mapper = new ModelMapper();
// 开启字段匹配,允许访问私有字段
mapper.getConfiguration()
    .setFieldMatchingEnabled(true)
    .setFieldAccessLevel(AccessLevel.PRIVATE)
    .setMatchingStrategy(MatchingStrategies.STRICT);

// 自定义TypeMap处理Employees类的映射
TypeMap<aaa.bbb.ccc.jar.Employees, aaa.bbb.ccc.jar.generated.Employees> typeMap = 
    mapper.createTypeMap(aaa.bbb.ccc.jar.Employees.class, aaa.bbb.ccc.jar.generated.Employees.class);

typeMap.addMappings(mapping -> {
    mapping.map(src -> src.getEmployees(), (dest, value) -> {
        if (value != null) {
            // 将源集合中的每个Employee映射到目标Employee并添加到目标集合
            List<aaa.bbb.ccc.jar.generated.Employee> destEmployees = dest.getEmployees();
            for (aaa.bbb.ccc.jar.Employee emp : (List<aaa.bbb.ccc.jar.Employee>) value) {
                destEmployees.add(mapper.map(emp, aaa.bbb.ccc.jar.generated.Employee.class));
            }
        }
    });
});

方案2:给生成的XML Employees类添加setter方法(不推荐,因为重新生成会覆盖)

如果允许修改生成的类,可以手动添加setEmployees()方法:

public void setEmployees(List<Employee> employees) {
    this.employees = employees;
}

这种方式简单,但每次重新生成JAXB类时都需要重新添加,维护成本高。

方案3:使用Converter手动处理集合映射

创建自定义Converter来处理两个Employees类之间的转换:

Converter<aaa.bbb.ccc.jar.Employees, aaa.bbb.ccc.jar.generated.Employees> employeesConverter = 
    context -> {
        aaa.bbb.ccc.jar.Employees source = context.getSource();
        aaa.bbb.ccc.jar.generated.Employees dest = new aaa.bbb.ccc.jar.generated.Employees();
        
        if (source.getEmployees() != null) {
            for (aaa.bbb.ccc.jar.Employee srcEmp : source.getEmployees()) {
                aaa.bbb.ccc.jar.generated.Employee destEmp = context.getMappingEngine().map(srcEmp, aaa.bbb.ccc.jar.generated.Employee.class);
                dest.getEmployees().add(destEmp);
            }
        }
        return dest;
    };

mapper.addConverter(employeesConverter);

内容的提问来源于stack exchange,提问作者sairn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 22:13:11