Jackson XmlMapper反序列化xmlns命名空间属性为null如何解决
问题根因
xmlns是XML规范定义的命名空间声明属性,不属于用户自定义的普通节点属性,Woodstox(XmlMapper默认依赖的StAX解析器)默认会对这类属性做特殊拦截处理,不会将其作为普通属性传入Jackson反序列化上下文,因此直接用@JacksonXmlProperty映射普通属性的写法无法读取到对应值,替换为id这类自定义属性可以正常读取就是这个原因。
可行解决方案
方案1:开启XmlMapper内置特性(推荐,Jackson XML 2.12+版本支持)
Jackson从2.12版本开始提供了专门的解析特性,开启后会将所有命名空间声明作为普通属性暴露给反序列化流程,无需修改实体类注解,仅需调整XmlMapper初始化逻辑即可:
import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser; // 构建XmlMapper时开启命名空间声明上报特性 XmlMapper xmlMapper = XmlMapper.builder() .enable(FromXmlParser.Feature.REPORT_NAMESPACE_DECLARATIONS_AS_ATTRIBUTES) .build(); // 原有反序列化逻辑无需改动 ModelTrees modelTrees = xmlMapper.readValue(xml, ModelTrees.class);
开启该特性后,原有实体类中@JacksonXmlProperty(isAttribute = true, localName = "xmlns")标记的字段就可以正常读取到默认命名空间的取值。如果需要读取带前缀的命名空间声明(比如xmlns:test="xxx"),只需要对应把localName设置为xmlns:test即可正常映射。
方案2:自定义反序列化器(兼容低版本Jackson)
如果使用的Jackson XML版本低于2.12,可以通过自定义反序列化器直接从底层StAX读取器获取当前节点的命名空间值:
- 编写自定义命名空间反序列化逻辑
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser; import javax.xml.stream.XMLStreamReader; import java.io.IOException; public class DefaultNamespaceDeserializer extends JsonDeserializer<String> { @Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { FromXmlParser xmlParser = (FromXmlParser) p; XMLStreamReader staxReader = xmlParser.getStaxReader(); // 传入空字符串即可获取当前节点的默认命名空间(即无前缀的xmlns属性值) return staxReader.getNamespaceURI(""); } }
- 在实体类对应字段上绑定自定义反序列化器
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @JacksonXmlRootElement(localName = "model_trees") public class ModelTrees { @JacksonXmlProperty(isAttribute = true, localName = "xmlns") @JsonDeserialize(using = DefaultNamespaceDeserializer.class) private String some_text1; @JacksonXmlProperty(localName = "tree_kind") private String treeKind; // 注意:私有字段必须补充对应getter/setter,否则Jackson无法正常访问字段赋值 public String getSome_text1() { return some_text1; } public void setSome_text1(String some_text1) { this.some_text1 = some_text1; } public String getTreeKind() { return treeKind; } public void setTreeKind(String treeKind) { this.treeKind = treeKind; } }
注意事项
- 如果实体类字段使用
private修饰,必须为字段添加对应的getter/setter方法,或者配置Jackson的类内字段访问权限,否则会因为访问权限问题出现字段赋值为null的情况。 - 方案1的特性是全局生效的,如果XML报文里存在大量带前缀的命名空间声明,开启后不会影响原有普通字段的反序列化逻辑,可以正常使用。
内容的提问来源于stack exchange,提问作者Lethargicaa
相关产品推荐
相关产品推荐

