Jackson XML反序列化ObjectNode无法获取数组值的方案咨询
Jackson XML反序列化重复节点丢失问题解决
问题说明
使用Jackson 2.10.5解析XML时,若XML包含重复子节点(如示例中COMPANYS下的多个COMPANY),用ObjectNode接收动态内容节点ENTITY时,仅会保留最后一个重复节点。由于ENTITY内容不固定,无法预先定义实体类,需解决该问题并寻找替代方案。
相关代码与结果
Java实体类
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import lombok.Data; @Data @JacksonXmlRootElement(localName = "TX") public class ServiceBusReq<T> { @JacksonXmlProperty(localName = "TX_HEADER") private ServiceBusHeaderReq txHeader; @JacksonXmlProperty(localName = "TX_EMB") private String txEmb; /** **Notice:** I cannot specify the creation of an Entity object because the content received here is variable */ @JacksonXmlProperty(localName = "ENTITY") private ObjectNode entity; private T msgObject; }
XML示例
<TX> <TX_HEADER> <SYS_HDR_LEN/> <SYS_PKG_VRSN>01</SYS_PKG_VRSN> <SYS_TTL_LEN/> <SYS_REQ_SEC_ID>402205</SYS_REQ_SEC_ID> <SYS_SND_SEC_ID>402205</SYS_SND_SEC_ID> </TX_HEADER> <TX_EMB/> <ENTITY> <USER_NAME>xcc</USER_NAME> <COMPANYS> <COMPANY> <COMPANYNAME>NAME-1</COMPANYNAME> </COMPANY> <COMPANY> <COMPANYNAME>NAME-2</COMPANYNAME> </COMPANY> </COMPANYS> </ENTITY> </TX>
反序列化代码
XmlMapper xmlMapper = XmlMapper.xmlBuilder().build(); ServiceBusReq serviceBusReq = xmlMapper.readValue(xml, ServiceBusReq.class); System.out.println(serviceBusReq);
错误输出
ServiceBusReq(txHeader=ServiceBusHeaderReq(sysHdrLen=null, sysPkgVrsn=01, sysTtlLen=null, sysReqSecId=402205, sysSndSecId=402205, sysTxCode=null, sysTxVrsn=null, sysTxType=null, sysReserved=null, sysEvtTraceId=null, sysSndSerialNo=null, sysPkgType=null, sysMsgLen=null, sysIsEncrypted=null, sysEncryptType=null, sysCompressType=null, sysEmbMsgLen=null, sysReqTime=null, sysTimeLeft=null, sysPkgStsType=null), txBody=null, txEmb=null, entity={"USER_NAME":"xcc","COMPANYS":{"COMPANY":{"COMPANYNAME":"NAME-2"}}}, msgObject=null)
解决方法与替代方案
方案1:配置XmlMapper并替换ObjectNode为JsonNode
Jackson默认会覆盖XML中的重复节点,需开启ACCEPT_SINGLE_VALUE_AS_ARRAY特性,同时将ObjectNode替换为JsonNode(默认解析逻辑对JsonNode更友好):
- 修改实体类字段类型:
import com.fasterxml.jackson.databind.JsonNode; // 替换原ObjectNode为JsonNode @JacksonXmlProperty(localName = "ENTITY") private JsonNode entity;
- 配置XmlMapper:
XmlMapper xmlMapper = XmlMapper.xmlBuilder() .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) .build();
解析后,COMPANYS下的COMPANY会被转为数组节点,可通过entity.get("COMPANYS").get("COMPANY")获取数组并遍历。
方案2:用Map<String, Object>接收动态内容
若无需JSON节点操作API,直接用Map<String, Object>接收ENTITY,Jackson会自动将重复节点转为List:
修改实体类字段:
@JacksonXmlProperty(localName = "ENTITY") private Map<String, Object> entity;
此时entity中COMPANYS对应的value是一个Map,其中COMPANY为包含两个元素的List,可直接遍历获取所有COMPANY节点。
方案3:自定义反序列化器(精细控制)
若需要递归处理所有嵌套的重复节点,可自定义反序列化器:
- 实现反序列化器:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class DynamicEntityDeserializer extends JsonDeserializer<JsonNode> { @Override public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.getCodec().readTree(p); return processDuplicateNodes(node); } private JsonNode processDuplicateNodes(JsonNode node) { if (node.isObject()) { ObjectNode objectNode = (ObjectNode) node; Iterator<Map.Entry<String, JsonNode>> fields = objectNode.fields(); Map<String, List<JsonNode>> fieldGroups = new HashMap<>(); // 收集所有字段,按名称分组 while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); fieldGroups.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(entry.getValue()); fields.remove(); } // 分组处理:多个值转为数组,单个值保持原样 for (Map.Entry<String, List<JsonNode>> entry : fieldGroups.entrySet()) { String key = entry.getKey(); List<JsonNode> values = entry.getValue(); if (values.size() > 1) { ArrayNode arrayNode = objectNode.arrayNode(); values.forEach(arrayNode::add); objectNode.set(key, arrayNode); } else { objectNode.set(key, values.get(0)); } } return objectNode; } else if (node.isArray()) { ArrayNode arrayNode = (ArrayNode) node; for (int i = 0; i < arrayNode.size(); i++) { arrayNode.set(i, processDuplicateNodes(arrayNode.get(i))); } return arrayNode; } return node; } }
- 在实体类字段上添加注解:
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @JacksonXmlProperty(localName = "ENTITY") @JsonDeserialize(using = DynamicEntityDeserializer.class) private JsonNode entity;
该方案会递归处理所有层级的重复节点,确保所有重复项都被转为数组。
内容的提问来源于stack exchange,提问作者EverSpring
相关产品推荐
相关产品推荐

