如何使用Jackson序列化反序列化带自定义列表名的不可变XML对象
问题根源
所有报错的核心原因是混淆了Jackson两层独立的映射逻辑:
- 第一层是Java侧的逻辑属性映射,靠
@JsonProperty关联,序列化、反序列化全靠这个逻辑名匹配,一旦两边逻辑名不一致就会报找不到creator属性、重复属性的错误。 - 第二层是XML结构的标签映射,这部分是Jackson XML模块独立的逻辑,靠
@JacksonXmlProperty、@JacksonXmlElementWrapper控制,完全不依赖@JsonProperty的配置。
之前的操作错把XML里列表子元素的标签名(message)塞到了@JsonProperty里,把列表的Java逻辑名从默认的messages改成了message,直接打乱了两层映射的对应关系:
- 不可变类用
@JsonCreator做属性注入时,每个构造器参数必须显式标注@JsonProperty对应的逻辑属性名,缺少这个配置就会抛出构造器参数无属性名的定义异常。 - 当getter上的
@JsonProperty设为message时,Jackson会把列表的逻辑属性名识别为message;但@JacksonXmlElementWrapper默认会把外层标签和逻辑属性名绑定,手动指定wrapper标签为messages后,反序列化时Jackson会把<messages>标签和逻辑属性messages关联,和构造器里标注的message参数对不上,抛出找不到creator属性的异常。 - 当调整构造器参数的
@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
相关产品推荐
相关产品推荐

