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> * <complexType name="Employee"> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <all> * <element name="Name" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/> * <element name="Gender" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/> * <element name="Age" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/> * </all> * </restriction> * </complexContent> * </complexType> * </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> * <complexType> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <sequence> * <element name="Employees" type="{http://www.aaa.bbb.ccc/jar/mmdemo}Employee" maxOccurs="unbounded" minOccurs="0"/> * </sequence> * </restriction> * </complexContent> * </complexType> * </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>
问题原因分析
- 目标类无集合setter方法:生成的XML
Employees类只有getEmployees()方法,没有setEmployees()方法,ModelMapper默认会尝试通过setter方法注入集合数据,找不到对应方法时无法完成映射。 - 跨包Employee类映射:源JSON的
Employee类(aaa.bbb.ccc.jar.Employee)和目标XML的Employee类(aaa.bbb.ccc.jar.generated.Employee)属于不同包,ModelMapper需要明确配置才能识别这两个类的字段映射关系。 - 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
相关产品推荐
相关产品推荐

