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

如何使用Retrofit解析这种特殊的XML结构?

解决Jackson XML解析交替元素结构的问题

问题背景

使用Retrofit搭配Jackson XML解析器时,遇到特殊XML结构:<child_group>内部是<item>与<info>交替出现的元素,并非常规同类型数组结构,现有数据类仅能解析第一组<item>和<info>,无法处理后续交替元素。

原始XML结构:

<?xml version="1.0" encoding="utf-8"?>
<parent id="2261">
    <child_group>
        <item>
            <line>1</line>
        </item>
        <info></info>
        <item>
            <line>2</line>
        </item>
        <info></info>
        <item>
            <line>3</line>
        </item>
        <info></info>
    </child_group>
</parent>

现有写法的问题

之前尝试的两种数据类结构均无法正确解析:

  1. 第一种将<child_group>映射为包含List<Item>和单个info的类,仅能读取所有<item>但仅保留第一个<info>;
  2. 第二种尝试将<child_group>映射为List<ChildGroupItem>,但Jackson会将每个<item>或<info>单独尝试匹配ChildGroupItem(该类同时要求item和info字段),导致后续元素因匹配失败被忽略。

解决方案

方法1:混合列表接收所有元素后手动分组

通过将<child_group>的所有子元素接收为混合类型列表,再手动将<item>和<info>配对:

@JacksonXmlRootElement(localName = "parent")
data class Parent(
    val id: String,
    @JacksonXmlProperty(localName = "child_group")
    val childGroup: ChildGroup
)

data class ChildGroup(
    @JacksonXmlElementWrapper(useWrapping = false)
    @JacksonXmlProperty(localName = "*") // 匹配所有子元素
    val elements: List<Any>
)

data class Item(
    val line: String
)

data class Info(
    @JacksonXmlText // 解析<info>的文本内容
    val content: String? = null
)

解析完成后,手动遍历列表完成配对:

// 示例:将交替的Item和Info分组为成对数据
val itemInfoPairs = parent.childGroup.elements.chunked(2) { pair ->
    val item = pair[0] as Item
    val info = pair.getOrNull(1) as? Info ?: Info(null)
    Pair(item, info)
}

方法2:自定义反序列化器(更优雅)

编写自定义反序列化器,直接将<child_group>的交替元素解析为成对的结构化数据:

1. 定义目标数据类

@JacksonXmlRootElement(localName = "parent")
data class Parent(
    val id: String,
    @JacksonXmlProperty(localName = "child_group")
    @JsonDeserialize(using = ChildGroupDeserializer::class) // 指定自定义反序列化器
    val childGroup: ChildGroup
)

data class ChildGroup(val itemsWithInfo: List<ItemWithInfo>)
data class ItemWithInfo(val item: Item, val info: Info)
data class Item(val line: String)
data class Info(@JacksonXmlText val content: String? = null)

2. 实现自定义反序列化器

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.deser.std.StdDeserializer

class ChildGroupDeserializer : StdDeserializer<ChildGroup>(ChildGroup::class.java) {
    constructor() : this(null)

    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): ChildGroup {
        val node = p.codec.readTree<JsonNode>(p)
        val elements = node.elements()
        val itemInfoPairs = mutableListOf<ItemWithInfo>()
        
        // 按交替规则读取Item和对应的Info
        while (elements.hasNext()) {
            val itemNode = elements.next()
            val item = p.codec.treeToValue(itemNode, Item::class.java)
            // 兼容可能缺少Info的情况
            val infoNode = if (elements.hasNext()) elements.next() else null
            val info = infoNode?.let { p.codec.treeToValue(it, Info::class.java) } ?: Info(null)
            itemInfoPairs.add(ItemWithInfo(item, info))
        }
        
        return ChildGroup(itemInfoPairs)
    }
}

3. Retrofit配置保持不变

确保XmlMapper正确注册Kotlin模块:

Retrofit.Builder().baseUrl("https://www.website.es/")
    .addConverterFactory(
        JacksonConverterFactory.create(XmlMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .registerKotlinModule()))
    .build().create(ApiService::class.java)

原理说明

  • 方法1利用Jackson的@JacksonXmlProperty(localName = "*")匹配所有子元素,将不同类型节点存入列表后手动处理配对逻辑;
  • 方法2通过自定义反序列化器直接控制解析流程,按照<item>+<info>的交替规则读取节点,直接映射为结构化成对数据,避免后续手动处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 13:53:12