@JacksonXmlElementWrapper使用限制
作用
@JacksonXmlElementWrapper 是 Jackson XML 模块提供的注解,用于控制Collection或Array字段在 XML 序列化/反序列化时是否使用包装元素,以及包装元素的名称和命名空间。
注:该注解只对Collection和Array字段有效。
行为解释
XmlMapper默认开启包装行为,可通过以下代码手动开启或关闭
XmlMapper mapper = XmlMapper.builder().defaultUseWrapper(false).build();
上面是一个全局开关,如果开启,即便字段上没有@JacksonXmlElementWrapper注解,也会添加包装元素,包装元素的名称就是属性名。
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface JacksonXmlElementWrapper
{
public final static String USE_PROPERTY_NAME = "";
String namespace() default USE_PROPERTY_NAME;
/**
* 包装元素名称
* 默认为字段名称(@JacksonXmlProperty注解可以指定字段名称,若没有明确指定,则就是java字段名)
*/
String localName() default USE_PROPERTY_NAME;
/**
* 用于控制是否添加包装元素, 可覆盖全局设置
*/
boolean useWrapping() default true;
}
使用示例
不加任何注解
public class XmlTest {
@AllArgsConstructor
@Data
public static class Item {
public String id;
public String name;
}
@AllArgsConstructor
@Data
public static class SingleContainer {
public List<Item> itemList;
}
@Test
public void testWrap() {
SingleContainer obj = new SingleContainer(
Arrays.asList(
new Item("1", "a"),
new Item("2", "b"))
);
String xml = XmlUtils.objToXml(obj);
System.out.println(xml);
}
}
结果如下:
<?xml version='1.0' encoding='UTF-8'?>
<SingleContainer>
<itemList>
<itemList>
<id>1</id>
<name>a</name>
</itemList>
<itemList>
<id>2</id>
<name>b</name>
</itemList>
</itemList>
</SingleContainer>
可以看到包装元素名称默认就是字段名。
使用@JacksonXmlProperty注解指定字段名
public class XmlTest {
@AllArgsConstructor
@Data
public static class Item {
public String id;
public String name;
}
@AllArgsConstructor
@Data
public static class SingleContainer {
@JacksonXmlProperty(localName = "item")
public List<Item> itemList;
}
@Test
public void testWrap() {
SingleContainer obj = new SingleContainer(
Arrays.asList(
new Item("1", "a"),
new Item("2", "b"))
);
String xml = XmlUtils.objToXml(obj);
System.out.println(xml);
}
}
结果
<?xml version='1.0' encoding='UTF-8'?>
<SingleContainer>
<item>
<item>
<id>1</id>
<name>a</name>
</item>
<item>
<id>2</id>
<name>b</name>
</item>
</item>
</SingleContainer>
可以看到包装元素会跟随字段名一起变化
使用@JacksonXmlElementWrapper
public class XmlTest {
@AllArgsConstructor
@Data
public static class Item {
public String id;
public String name;
}
@AllArgsConstructor
@Data
public static class SingleContainer {
@JacksonXmlElementWrapper(localName = "itemList")
@JacksonXmlProperty(localName = "item")
public List<Item> itemList;
}
@Test
public void testWrap() {
SingleContainer obj = new SingleContainer(
Arrays.asList(
new Item("1", "a"),
new Item("2", "b"))
);
String xml = XmlUtils.objToXml(obj);
System.out.println(xml);
}
}
结果
<?xml version='1.0' encoding='UTF-8'?>
<SingleContainer>
<itemList>
<item>
<id>1</id>
<name>a</name>
</item>
<item>
<id>2</id>
<name>b</name>
</item>
</itemList>
</SingleContainer>
可以看到包装元素名称变成了itemList。
也就是说@JacksonXmlElementWrapper指定包装元素名,@JacksonXmlProperty指定集合项元素名,若@JacksonXmlElementWrapper没有指定,包装元素名就是@JacksonXmlProperty的值。而@JacksonXmlProperty的值默认为java字段名,当然也可自己指定。
使用限制
如果一个类中有多个集合类型,想要包装元素名不一样,而集合项元素名一样,使用这两个注解配合是无法做到的。因为集合项元素名是通过@JacksonXmlProperty指定,如果一样则会报名字冲突。就像java类中字段名也不能冲突一样。
public class XmlTest {
@AllArgsConstructor
@Data
public static class Item {
public String id;
public String name;
}
@AllArgsConstructor
@Data
public static class MultiContainer {
@JacksonXmlElementWrapper(localName = "itemList1")
@JacksonXmlProperty(localName = "item")
public List<Item> itemList1;
@JacksonXmlElementWrapper(localName = "itemList2")
@JacksonXmlProperty(localName = "item")
public List<Item> itemList2;
}
@Test
public void testWrap() {
MultiContainer obj = new MultiContainer(
Arrays.asList(new Item("1", "a"), new Item("2", "b")),
Arrays.asList(new Item("3", "a"), new Item("4", "b"))
);
String xml = XmlUtils.objToXml(obj);
System.out.println(xml);
}
}
报错如下
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Conflicting getter definitions for property "item"
使用自定义序列化解决如上问题
实现
包装元素还是交由@JacksonXmlElementWrapper指定,自定义序列化器用来替换集合元素项名称。
XmlElement.java
/**
* 自定义注解,用于指定 XML 元素名称。
* 配合 {@link ObjectXmlSerializer} 使用,可在序列化集合类型时指定集合项元素名称。
* <p>
* 设计目的:
* 当同一个对象中有多个集合类型(如 List),且希望它们的包装名不同,但集合项元素名称相同时,
* 使用 Jackson 原生的 {@code @JacksonXmlElementWrapper} 和 {@code @JacksonXmlProperty} 注解无法满足需求,
* 通过本注解 + {@link ObjectXmlSerializer} 可以实现该功能。
* </p>
*
* @see ObjectXmlSerializer
*/
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XmlElement {
/**
* 指定 XML 元素的本地名称(即标签名)。
*
* @return 元素名称
*/
String localName();
}
ObjectXmlSerializer.java
/**
* 自定义 Jackson 序列化器,用于指定 XML 元素的标签名。
* 实现 {@link ContextualSerializer} 接口,以便在序列化时动态获取 {@link XmlElement} 注解中指定的名称。
* <p>
* 使用方式:
* 在需要自定义名称的 List 或容器字段上,同时标注:
* <pre>{@code
* @XmlElement(localName = "bean")
* @JsonSerialize(contentUsing = ObjectXmlSerializer.class)
* private List<Bean> beanList;
* }</pre>
* <p>
* 序列化结果示例:
* <pre>{@code
* <ContainerBean>
* <BeanList1>
* <bean>...</bean>
* </BeanList1>
* <BeanList2>
* <bean>...</bean>
* </BeanList2>
* </ContainerBean>
* }</pre>
*
* @see XmlElement
* @author wangtao20
* Created at 2026-08-19
*/
public class ObjectXmlSerializer extends JsonSerializer<Object> implements ContextualSerializer {
/**
* XML元素名称
*/
private String elementName;
public ObjectXmlSerializer() {
}
/**
* 带元素名称的构造方法,由 {@link #createContextual} 调用。
*
* @param elementName XML元素名称
*/
public ObjectXmlSerializer(String elementName) {
this.elementName = elementName;
}
/**
* 序列化方法,覆盖当前节点的元素名称。
*
* @param object 要序列化的对象
* @param gen JSON 生成器(实际为 {@link ToXmlGenerator})
* @param serializers 序列化提供者
* @throws IOException 序列化异常
*/
@Override
public void serialize(Object object, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (this.elementName == null) {
throw new IllegalArgumentException("elementName is null, please use @XmlElement to set the element name");
}
/*
* 此时的nextName为@JacksonXmlProperty指定的名称
* 将当前节点元素名替换为指定的elementName
*/
ToXmlGenerator xmlGen = (ToXmlGenerator) gen;
xmlGen.setNextName(new QName(this.elementName));
// 委托给默认序列化器处理值内容
serializers.defaultSerializeValue(object, gen);
}
/**
* 上下文序列化工厂方法,用于从字段/方法上读取 {@link XmlElement} 注解,
* 并构造带有指定元素名称的序列化器实例。
*
* @param prov 序列化提供者
* @param property 当前序列化的属性(字段或方法)
* @return 如果注解存在则返回新的 {@link ObjectXmlSerializer} 实例,否则返回 null(表示不干预)
*/
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
XmlElement annotation = property.getAnnotation(XmlElement.class);
// 若用户指定了容器元素名称,则使用用户指定的名称
if (annotation != null) {
return new ObjectXmlSerializer(annotation.localName());
}
return this;
}
}
提供一个默认的集合项元素名为sdo的序列化器,与JAXB类似。
SdoObjectXmlSerializer.java
/**
* 默认集合项元素名称为sdo, 与JAXB风格保持一致
*/
public class SdoObjectXmlSerializer extends ObjectXmlSerializer {
public SdoObjectXmlSerializer() {
super("sdo");
}
}
使用1
public class XmlTest {
@AllArgsConstructor
@Data
public static class Item {
public String id;
public String name;
}
@AllArgsConstructor
@Data
public static class MultiContainer {
/**
* @JacksonXmlElementWrapper可以省略,默认值为java字段名
* @JsonSerialize使用contentUsing而不是using
* contentUsing针对的是集合中的元素
*/
@JsonSerialize(contentUsing = ObjectXmlSerializer.class)
@XmlElement(localName = "item")
@JacksonXmlElementWrapper(localName = "itemList1")
public List<Item> itemList1;
@JsonSerialize(contentUsing = ObjectXmlSerializer.class)
@XmlElement(localName = "item")
@JacksonXmlElementWrapper(localName = "itemList2")
public List<Item> itemList2;
}
@Test
public void testWrap() {
MultiContainer obj = new MultiContainer(
Arrays.asList(new Item("1", "a"), new Item("2", "b")),
Arrays.asList(new Item("3", "a"), new Item("4", "b"))
);
String xml = XmlUtils.objToXml(obj);
System.out.println(xml);
}
}
结果
<?xml version='1.0' encoding='UTF-8'?>
<MultiContainer>
<itemList1>
<item>
<id>1</id>
<name>a</name>
</item>
<item>
<id>2</id>
<name>b</name>
</item>
</itemList1>
<itemList2>
<item>
<id>3</id>
<name>a</name>
</item>
<item>
<id>4</id>
<name>b</name>
</item>
</itemList2>
</MultiContainer>
使用2
public class XmlTest {
@AllArgsConstructor
@Data
public static class Item {
public String id;
public String name;
}
@AllArgsConstructor
@Data
public static class MultiContainer {
@JsonSerialize(contentUsing = SdoObjectXmlSerializer.class)
public List<Item> itemList1;
@JsonSerialize(contentUsing = SdoObjectXmlSerializer.class)
public List<Item> itemList2;
}
@Test
public void testWrap() {
MultiContainer obj = new MultiContainer(
Arrays.asList(new Item("1", "a"), new Item("2", "b")),
Arrays.asList(new Item("3", "a"), new Item("4", "b"))
);
String xml = XmlUtils.objToXml(obj);
System.out.println(xml);
}
}
结果
<?xml version='1.0' encoding='UTF-8'?>
<MultiContainer>
<itemList1>
<sdo>
<id>1</id>
<name>a</name>
</sdo>
<sdo>
<id>2</id>
<name>b</name>
</sdo>
</itemList1>
<itemList2>
<sdo>
<id>3</id>
<name>a</name>
</sdo>
<sdo>
<id>4</id>
<name>b</name>
</sdo>
</itemList2>
</MultiContainer>
浙公网安备 33010602011771号