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

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更友好):

  1. 修改实体类字段类型:
import com.fasterxml.jackson.databind.JsonNode;

// 替换原ObjectNode为JsonNode
@JacksonXmlProperty(localName = "ENTITY")
private JsonNode entity;
  1. 配置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:自定义反序列化器(精细控制)

若需要递归处理所有嵌套的重复节点,可自定义反序列化器:

  1. 实现反序列化器:
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;
    }
}
  1. 在实体类字段上添加注解:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 17:15:38