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

如何使用Jackson序列化反序列化带自定义列表名的不可变XML对象

问题根源

所有报错的核心原因是混淆了Jackson两层独立的映射逻辑:

  • 第一层是Java侧的逻辑属性映射,靠@JsonProperty关联,序列化、反序列化全靠这个逻辑名匹配,一旦两边逻辑名不一致就会报找不到creator属性、重复属性的错误。
  • 第二层是XML结构的标签映射,这部分是Jackson XML模块独立的逻辑,靠@JacksonXmlProperty、@JacksonXmlElementWrapper控制,完全不依赖@JsonProperty的配置。

之前的操作错把XML里列表子元素的标签名(message)塞到了@JsonProperty里,把列表的Java逻辑名从默认的messages改成了message,直接打乱了两层映射的对应关系:

  1. 不可变类用@JsonCreator做属性注入时,每个构造器参数必须显式标注@JsonProperty对应的逻辑属性名,缺少这个配置就会抛出构造器参数无属性名的定义异常。
  2. 当getter上的@JsonProperty设为message时,Jackson会把列表的逻辑属性名识别为message;但@JacksonXmlElementWrapper默认会把外层标签和逻辑属性名绑定,手动指定wrapper标签为messages后,反序列化时Jackson会把<messages>标签和逻辑属性messages关联,和构造器里标注的message参数对不上,抛出找不到creator属性的异常。
  3. 当调整构造器参数的@JsonProperty值去匹配getter的message逻辑名时,因为getter方法名getMessages本身对应默认逻辑名messages,Jackson会识别出两个独立的重复属性,抛出Duplicate property异常。
正确配置方法

核心逻辑:锁死Java侧的逻辑属性名不动,用XML专属注解控制所有标签结构

  • 统一列表属性的Java逻辑名为messages:构造器参数、getter上的@JsonProperty值都设为"messages",保证Jackson能识别到两边是同一个逻辑属性,从根源上消除属性不匹配、重复的问题。
  • 移除getter上错误的@JsonProperty("message")配置,改用@JacksonXmlProperty(localName = "message")标注列表内每个元素的XML标签名——这个注解是XML模块专属的,只会控制序列化时子元素的标签名,完全不改变Java侧的逻辑属性名。
  • 保留@JacksonXmlElementWrapper(localName = "messages")配置,控制列表外层的包裹标签。
  • 给嵌套的不可变类Message的构造器参数补充@JsonProperty("code")标注,匹配对应的逻辑属性名,避免反序列化时的同类报错。
修正后的可运行代码
public class Playground {

    public static void main(String[] args) throws JsonProcessingException {
        Response response = new Response("response-id", List.of(new Message("a"), new Message("b")));

        XmlMapper xmlObjectMapper = new XmlMapper(new XmlFactory());
        xmlObjectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
        final String value = xmlObjectMapper.writeValueAsString(response);
        System.out.println(value);
        assert ("<response id=\"response-id\"><messages><message code=\"a\"/><message "
            + "code=\"b\"/></messages></response>")
            .equals(value) : "Desired format does not match!";
        final Response deserializedValue = xmlObjectMapper.readValue(value, Response.class);
        final String roundTripValue = xmlObjectMapper.writeValueAsString(deserializedValue);
        assert value.equals(roundTripValue) : "Round-trip conversion failed";
    }
}

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JacksonXmlRootElement(localName = "response")
@JsonPropertyOrder({"id", "messages"})
class Response {

    private final String id;
    private final List<Message> messages;

    @JsonCreator
    public Response(
        @JacksonXmlProperty(localName = "id", isAttribute = true)
        @JsonProperty("id") final String id,
        @JsonProperty("messages") final List<Message> messages) {
        this.id = id;
        this.messages = messages;
    }

    @JacksonXmlProperty(localName = "id", isAttribute = true)
    public String getId() {
        return id;
    }

    @JsonProperty("messages")
    @JacksonXmlElementWrapper(localName = "messages")
    @JacksonXmlProperty(localName = "message")
    public List<Message> getMessages() {
        return messages;
    }
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
class Message {

    private final String code;

    public Message(
        @JacksonXmlProperty(localName = "code", isAttribute = true)
        @JsonProperty("code") final String code) {
        this.code = code;
    }

    @JacksonXmlProperty(localName = "code", isAttribute = true)
    public String getCode() {
        return code;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.22 08:54:02