Jackson XML反序列化:带可选属性的单值复杂属性处理问题
解决Jackson XML反序列化兼容带/不带属性的Provider元素问题
问题根源
你遇到的类型不匹配异常,主要有两个原因:
- 误用
@JacksonXmlElementWrapper注解:该注解仅用于集合类型属性(如List<ProviderDto>),用来指定集合的外层包裹标签。在单个ProviderDto属性上使用它,会导致Jackson错误地将单个<Provider>元素解析为集合类型,引发类型转换异常。 - Jackson默认解析行为:对于没有属性的
<Provider>文本内容</Provider>,Jackson会默认将其解析为字符串类型,而非映射到ProviderDto对象,当属性类型是ProviderDto时就会抛出类型不匹配异常。
分步解决方案
1. 移除错误的注解
首先删除父类属性上的@JacksonXmlElementWrapper(localName = "Provider")注解,修改后的属性定义应为:
ProviderDto provider;
2. 自定义反序列化器兼容两种场景
编写自定义反序列化器,让Jackson既能处理带Name属性的<Provider>,也能处理纯文本的<Provider>:
步骤1:实现JsonDeserializer
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 java.io.IOException; public class ProviderDtoDeserializer extends JsonDeserializer<ProviderDto> { @Override public ProviderDto deserialize(JsonParser parser, DeserializationContext context) throws IOException { JsonNode node = parser.readValueAsTree(); // 处理纯文本类型的Provider(无属性) if (node.isTextual()) { return ProviderDto.builder() .value(node.asText().trim()) .build(); } // 处理带属性的Provider String name = node.has("name") ? node.get("name").asText().trim() : null; String value = node.asText().trim(); return ProviderDto.builder() .name(name) .value(value) .build(); } }
步骤2:在ProviderDto上注册反序列化器
在ProviderDto类上添加@JsonDeserialize注解,指定使用上面的自定义反序列化器:
@Value @Builder @Jacksonized @JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class) @JsonInclude(JsonInclude.Include.NON_EMPTY) @JacksonXmlRootElement(localName = "Provider") @JsonIgnoreProperties(ignoreUnknown = true) @JsonDeserialize(using = ProviderDtoDeserializer.class) // 添加这行 public class ProviderDto { @JacksonXmlProperty(isAttribute = true) String name; @JacksonXmlText String value; }
替代方案:使用@JsonCreator处理多类型输入
如果你不想编写自定义反序列化器,可以在ProviderDto中添加一个静态的@JsonCreator方法,直接处理字符串和对象两种输入类型:
@Value @Builder @Jacksonized @JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class) @JsonInclude(JsonInclude.Include.NON_EMPTY) @JacksonXmlRootElement(localName = "Provider") @JsonIgnoreProperties(ignoreUnknown = true) public class ProviderDto { @JacksonXmlProperty(isAttribute = true) String name; @JacksonXmlText String value; @JsonCreator public static ProviderDto fromInput(Object input) { if (input instanceof String) { return ProviderDto.builder() .value(((String) input).trim()) .build(); } else if (input instanceof Map) { @SuppressWarnings("unchecked") Map<String, Object> inputMap = (Map<String, Object>) input; String name = inputMap.containsKey("name") ? inputMap.get("name").toString().trim() : null; String value = inputMap.getOrDefault("", "").toString().trim(); return ProviderDto.builder() .name(name) .value(value) .build(); } throw new IllegalArgumentException("无法解析输入类型: " + input.getClass().getName()); } }
验证测试
修改完成后,分别测试两种XML场景:
- 带属性的情况:
<Coordinate> <Provider Name="Android 11.0">Vodafone</Provider> </Coordinate>
- 纯文本的情况:
<NameAndAddress> <Provider>Virgin Media</Provider> </NameAndAddress>
两种场景都应该能正常反序列化为对应的ProviderDto对象,不会再抛出类型不匹配异常。
内容的提问来源于stack exchange,提问作者greengold
相关产品推荐
相关产品推荐

