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

Jackson XML反序列化:带可选属性的单值复杂属性处理问题

解决Jackson XML反序列化兼容带/不带属性的Provider元素问题

问题根源

你遇到的类型不匹配异常,主要有两个原因:

  1. 误用@JacksonXmlElementWrapper注解:该注解仅用于集合类型属性(如List<ProviderDto>),用来指定集合的外层包裹标签。在单个ProviderDto属性上使用它,会导致Jackson错误地将单个<Provider>元素解析为集合类型,引发类型转换异常。
  2. 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场景:

  1. 带属性的情况:
<Coordinate>
    <Provider Name="Android 11.0">Vodafone</Provider>
</Coordinate>
  1. 纯文本的情况:
<NameAndAddress>
    <Provider>Virgin Media</Provider>
</NameAndAddress>

两种场景都应该能正常反序列化为对应的ProviderDto对象,不会再抛出类型不匹配异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 00:27:17