如何将含同名<meta>标签的XML直接映射为POJO?
问题
从API拿到的XML结构是这样的:
<xml> <meta-info> <meta name="id" content="1234-5678"/> <meta name="title" content="test"/> <meta name="fileType" content="txt"/> <meta name="priority" content="low"/> </meta-info> </xml>
想要直接映射到一个带String id、title、fileType、priority属性的POJO里,试了两种方法都踩坑:
- 用Jackson的
@XmlPath注解时,抛了UnrecognizedPropertyException,说识别不了meta-info字段; - 换成JAXB反序列化,没报错但所有属性全是null。
解决方案
方案一:用Jackson XML扩展搞定
首先得确保项目里引入了jackson-dataformat-xml依赖,然后修正POJO的注解配置:
方式1:用正确的@XmlPath路径+忽略未知属性
之前的@XmlPath路径写错了,而且没配置忽略未映射的meta-info字段,导致报错。修正后的POJO:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.fasterxml.jackson.dataformat.xml.annotation.XmlPath; @JacksonXmlRootElement(localName = "xml") @JsonIgnoreProperties(ignoreUnknown = true) // 忽略没映射的meta-info字段 public class XmlMapTest { @XmlPath("meta-info/meta[@name='id']/@content") private String id; @XmlPath("meta-info/meta[@name='title']/@content") private String title; @XmlPath("meta-info/meta[@name='fileType']/@content") private String fileType; @XmlPath("meta-info/meta[@name='priority']/@content") private String priority; // 别忘了加getter和setter public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getFileType() { return fileType; } public void setFileType(String fileType) { this.fileType = fileType; } public String getPriority() { return priority; } public void setPriority(String priority) { this.priority = priority; } }
反序列化代码:
import com.fasterxml.jackson.dataformat.xml.XmlMapper; XmlMapper xmlMapper = new XmlMapper(); XmlMapTest result = xmlMapper.readValue(xmlString, XmlMapTest.class);
方式2:通过辅助类接收meta标签
如果不想用XmlPath,可以定义一个内部类接收单个meta标签,再通过setter分配属性:
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; @JacksonXmlRootElement(localName = "xml") public class XmlMapTest { private String id; private String title; private String fileType; private String priority; @JacksonXmlProperty(localName = "meta") @JacksonXmlElementWrapper(localName = "meta-info", useWrapping = true) public void setMeta(Meta meta) { switch (meta.getName()) { case "id": this.id = meta.getContent(); break; case "title": this.title = meta.getContent(); break; case "fileType": this.fileType = meta.getContent(); break; case "priority": this.priority = meta.getContent(); break; } } // getter方法按需添加 public String getId() { return id; } public String getTitle() { return title; } public String getFileType() { return fileType; } public String getPriority() { return priority; } // 内部类,映射单个meta标签 public static class Meta { @JacksonXmlProperty(isAttribute = true) private String name; @JacksonXmlProperty(isAttribute = true) private String content; // getter和setter public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } }
方案二:用JAXB实现映射
JAXB需要正确处理嵌套的meta-info和meta标签,有两种实现方式:
方式1:用适配器转成Map
先写个适配器类,把<meta>标签转成Map:
import javax.xml.bind.annotation.adapters.XmlAdapter; import java.util.HashMap; import java.util.List; import java.util.Map; public class MetaAdapter extends XmlAdapter<MetaAdapter.MetaList, Map<String, String>> { public static class MetaList { public List<Meta> meta; } public static class Meta { public String name; public String content; } @Override public Map<String, String> unmarshal(MetaList metaList) throws Exception { Map<String, String> metaMap = new HashMap<>(); for (Meta meta : metaList.meta) { metaMap.put(meta.name, meta.content); } return metaMap; } @Override public MetaList marshal(Map<String, String> map) throws Exception { // 序列化逻辑按需写,这里暂时返回null return null; } }
然后修改POJO,用适配器映射:
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.util.Map; @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class XmlMapTest { @XmlJavaTypeAdapter(MetaAdapter.class) private Map<String, String> metaInfo; // 通过Map获取属性 public String getId() { return metaInfo != null ? metaInfo.get("id") : null; } public String getTitle() { return metaInfo != null ? metaInfo.get("title") : null; } public String getFileType() { return metaInfo != null ? metaInfo.get("fileType") : null; } public String getPriority() { return metaInfo != null ? metaInfo.get("priority") : null; } public void setMetaInfo(Map<String, String> metaInfo) { this.metaInfo = metaInfo; } }
方式2:嵌套类直接映射
直接定义嵌套类对应meta-info和meta标签,然后通过方法匹配属性:
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlAttribute; import java.util.List; @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) public class XmlMapTest { @XmlElement(name = "meta-info") private MetaInfo metaInfo; // 获取属性的方法 public String getId() { return findMetaContent("id"); } public String getTitle() { return findMetaContent("title"); } public String getFileType() { return findMetaContent("fileType"); } public String getPriority() { return findMetaContent("priority"); } private String findMetaContent(String name) { if (metaInfo == null || metaInfo.metas == null) { return null; } for (Meta meta : metaInfo.metas) { if (name.equals(meta.name)) { return meta.content; } } return null; } // 嵌套类对应meta-info标签 public static class MetaInfo { @XmlElement(name = "meta") private List<Meta> metas; } // 嵌套类对应meta标签 public static class Meta { @XmlAttribute private String name; @XmlAttribute private String content; // getter和setter public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } }
JAXB的反序列化代码不变:
import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import java.io.StringReader; JAXBContext jc = JAXBContext.newInstance(XmlMapTest.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); XmlMapTest xmlResult = (XmlMapTest) unmarshaller.unmarshal(new StringReader(xml));
内容的提问来源于stack exchange,提问作者Frank de la torre
相关产品推荐
相关产品推荐

