如何使用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>
现有写法的问题
之前尝试的两种数据类结构均无法正确解析:
- 第一种将
<child_group>映射为包含List<Item>和单个info的类,仅能读取所有<item>但仅保留第一个<info>; - 第二种尝试将
<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
相关产品推荐
相关产品推荐

