使用Jackson反序列化含列表的XML时遇ArrayList反序列化异常如何解决?
兄弟,这个坑我之前刚踩过!你遇到的Exception in thread "main" com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.util.ArrayList out of VALUE_STRING token本质是Jackson XML反序列化的常见问题:当XML里的<bar>元素只有单个实例时,Jackson会默认把它解析成字符串(VALUE_STRING token),但你的Java字段定义的是List类型,类型不匹配就抛出异常了。下面给你几个靠谱的解决办法,按优先级推荐:
方法1:用Jackson XML注解精准控制(推荐)
这是最精准的方式,只针对特定字段生效,不会影响全局配置。核心是用@JacksonXmlElementWrapper和@JacksonXmlProperty组合:
@JacksonXmlElementWrapper(useWrapping = false):告诉Jackson不要期望<bar>有外层包裹元素(比如<bars><bar>...</bar></bars>),直接把单个或多个<bar>解析成List。@JacksonXmlProperty(localName = "bar"):指定XML中对应的元素名。
示例代码:
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import java.util.List; @JacksonXmlRootElement(localName = "foo") public class Foo { // 针对bar字段配置,单个或多个<bar>都会被解析成List<String> @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "bar") private List<String> barList; // 别忘了getter和setter public List<String> getBarList() { return barList; } public void setBarList(List<String> barList) { this.barList = barList; } }
不管你的XML是<foo><bar>test1</bar></foo>(单个bar)还是<foo><bar>test1</bar><bar>test2</bar></foo>(多个bar),都能正常解析成List<String>。
方法2:全局配置Jackson允许单个值转数组
如果你的项目里有很多类似的List字段,都需要处理单个元素转List的情况,可以全局开启Jackson的ACCEPT_SINGLE_VALUE_AS_ARRAY特性,这样所有List类型的字段都会自动把单个值当成数组处理。
示例代码:
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.dataformat.xml.XmlMapper; public class XmlDeserializeDemo { public static void main(String[] args) throws Exception { XmlMapper xmlMapper = new XmlMapper(); // 开启单个值转数组的特性 xmlMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); // 测试单个bar的XML String singleBarXml = "<foo><bar>hello</bar></foo>"; Foo foo = xmlMapper.readValue(singleBarXml, Foo.class); System.out.println(foo.getBarList()); // 输出 [hello] // 测试多个bar的XML String multiBarXml = "<foo><bar>hello</bar><bar>world</bar></foo>"; Foo foo2 = xmlMapper.readValue(multiBarXml, Foo.class); System.out.println(foo2.getBarList()); // 输出 [hello, world] } }
注意:这个是全局生效,如果你有某些List字段不需要这种处理,就别用这个方法,还是方法1更灵活。
方法3:自定义反序列化器(复杂场景用)
如果上面两种方法都满足不了你的需求(比如XML结构特别复杂,有嵌套或者特殊格式),可以写自定义的反序列化器,手动处理单个值和数组的情况。
示例代码:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import java.io.IOException; import java.util.ArrayList; import java.util.List; @JacksonXmlRootElement(localName = "foo") public class Foo { // 指定用自定义反序列化器处理这个字段 @JsonDeserialize(using = SingleOrListDeserializer.class) private List<String> barList; // getter和setter public List<String> getBarList() { return barList; } public void setBarList(List<String> barList) { this.barList = barList; } } // 自定义反序列化器,处理单个值转List的逻辑 class SingleOrListDeserializer extends JsonDeserializer<List<String>> { @Override public List<String> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { List<String> resultList = new ArrayList<>(); // 如果当前是单个标量值(比如字符串),直接添加到列表 if (p.getCurrentToken().isScalarValue()) { resultList.add(p.getText()); } // 如果是数组,遍历所有元素添加到列表 else if (p.getCurrentToken().isStartArray()) { while (p.nextToken() != null && !p.getCurrentToken().isEndArray()) { resultList.add(p.getText()); } } return resultList; } }
这种方式灵活性最高,但代码量也大,适合特殊场景下使用。
内容的提问来源于stack exchange,提问作者Damien-Amen

