Jackson 序列化:将字段值设置为 XML 元素名称
Posted
技术标签:
【中文标题】Jackson 序列化:将字段值设置为 XML 元素名称【英文标题】:Jackson Serialization: Setting field value as XML element name 【发布时间】:2015-06-13 11:46:07 【问题描述】:我们在基于 jax-rs 的 REST API 项目中使用 Jackson jax-rs XML 内容提供程序来处理 XML 内容类型。 在序列化 POJO 列表时,我们需要从 POJO 中的字段动态设置 xml 元素名称。
public class ResponsePOJO
@JacksonXmlProperty
@JacksonXmlElementWrapper(useWrapping = false)
private List<Message> message = new ArrayList<Message>();
public class Message
private String type; // "Error" or "Warning"
private String msg; // The actual message
默认 Jackson 序列化 XML:
<ResponsePOJO>
<message>
<type>Error</type>
<msg>Some random error message</msg>
</message>
<message>
<type>Warning</type>
<msg>Some random warning message</msg>
</message>
</ResponsePOJO>
我们的要求,即设置type为XML元素名称。
<ResponsePOJO>
<Error>
<msg>Some random error message</msg>
</Error>
<Warning>
<msg>Some random warning message</msg>
</Warning>
</ResponsePOJO>
为了实现这一点,我们以如下方式编写了一个自定义 XML 序列化器:
public class MessageListSerializer extends
JsonSerializer<List<Message>>
@Override
public void serialize(List<Message> value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException
for(Message me : value)
jgen.writeObjectField(me.getType(), me);
并使用注解添加序列化器:
@JacksonXmlProperty
@JacksonXmlElementWrapper(useWrapping = false)
@JsonSerialize(using=MessageListSerializer.class)
private List<Message> message = new ArrayList<Message>();
但是在使用 Jackson XMLMapper 序列化 ResponsePOJO 时,我们遇到了以下异常...
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Array index out of range: -2
at com.fasterxml.jackson.dataformat.xml.ser.XmlSerializerProvider.serializeValue(XmlSerializerProvider.java:100)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:2866)
at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:2289)
Caused by: java.lang.ArrayIndexOutOfBoundsException: Array index out of range: -2
at com.ctc.wstx.sw.BufferingXmlWriter.writeRaw(BufferingXmlWriter.java:241)
at com.ctc.wstx.sw.BaseStreamWriter.writeRaw(BaseStreamWriter.java:1113)
at com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator.writeRaw(ToXmlGenerator.java:592)
at com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter$Lf2SpacesIndenter.writeIndentation(DefaultXmlPrettyPrinter.java:517)
at com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter.writeEndObject(DefaultXmlPrettyPrinter.java:223)
at com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator.writeEndObject(ToXmlGenerator.java:422)
at com.fasterxml.jackson.dataformat.xml.ser.XmlBeanSerializer.serialize(XmlBeanSerializer.java:119)
at com.fasterxml.jackson.dataformat.xml.ser.XmlSerializerProvider.serializeValue(XmlSerializerProvider.java:92)
... 3 more
你能帮我解决这个问题吗...
【问题讨论】:
【参考方案1】:我认为你的方法过于复杂。我会将您的消息类重组为:
public class Message
private String msg; // The actual message
,并根据类型对其进行子类化:
public class Error extends Message
public class Warning extends Message
.
此外,这种方法允许您为每种类型添加自定义字段,更加灵活。
【讨论】:
感谢您的回复。即使在这种情况下,我们如何将“错误”和“警告”设置为 XML 元素名称,而不为“错误”和“警告”消息创建单独的列表。 更具体地说,我需要维护这些消息的顺序,因此使用单个列表 List“我无法在评论中发布它,因为它太长了” 以下是自定义类:
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class MyResponse
@XmlElements( @XmlElement(name = "error", type = MyError.class),
@XmlElement(name = "warning", type = MyWarning.class) )
@XmlElementWrapper
private List<MyMessage> messages = Lists.newArrayList();
public List<MyMessage> getMessages()
return messages;
public void setMessages(List<MyMessage> messages)
this.messages = messages;
@XmlAccessorType(XmlAccessType.FIELD)
public class MyMessage
protected String text;
public String getText()
return text;
public void setText(String text)
this.text = text;
@XmlAccessorType(XmlAccessType.FIELD)
public class MyError extends MyMessage
@XmlAccessorType(XmlAccessType.FIELD)
public class MyWarning extends MyMessage
我用我的演示代码对其进行了测试:
MyResponse myResponse = new MyResponse();
MyMessage error = new MyError();
error.setText("error");
MyMessage warning = new MyWarning();
warning.setText("warning");
myResponse.setMessages(Lists.newArrayList(error, warning));
它返回:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myResponse>
<messages>
<error>
<text>error</text>
</error>
<warning>
<text>warning</text>
</warning>
</messages>
</myResponse>
您需要调整元素名称以获得所需的结果。
【讨论】:
如果您不想看到编辑到以前的解决方案:
快到了,只需将@JsonIgnore
添加到private String type; // "Error" or "Warning"
<ResponsePOJO>
<Error>
<msg>error message</msg>
</Error>
<Warning>
<msg>warning message</msg>
</Warning>
</ResponsePOJO>
下面会输出上面的xml:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import java.util.ArrayList;
import java.util.List;
public class Main
public static void main(String[] args)
Main demo = new Main();
demo.run();
public void run()
ObjectMapper xmlMapper = new XmlMapper();
ResponsePOJO responsePOJO = new ResponsePOJO();
Message message = new Message();
message.setType("Error");
message.setMsg("error message");
Message message2 = new Message();
message2.setType("Warning");
message2.setMsg("warning message");
responsePOJO.getMessage().add(message);
responsePOJO.getMessage().add(message2);
try
String xml = xmlMapper.writeValueAsString(responsePOJO);
System.out.println(xml);
catch (JsonProcessingException e)
e.printStackTrace();
public class ResponsePOJO
@JacksonXmlProperty
@JacksonXmlElementWrapper(useWrapping = false)
@JsonSerialize(using=MessageListSerializer.class)
private List<Message> message = new ArrayList<Message>();
public List<Message> getMessage()
return message;
public class Message
@JsonIgnore
private String type; // "Error" or "Warning"
private String msg; // The actual message
public String getType()
return type;
public void setType(String type)
this.type = type;
public String getMsg()
return msg;
public void setMsg(String msg)
this.msg = msg;
与班级一起
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.List;
/**
* Created by Pand on 08/04/2015.
*/
public class MessageListSerializer extends
JsonSerializer<List<Main.Message>>
@Override
public void serialize(List<Main.Message> value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException
for(Main.Message me : value)
jgen.writeObjectField(me.getType(), me);
有依赖关系
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
<version>4.1.4</version>
</dependency>
</dependencies>
【讨论】:
创建多个列表来存储错误和警告消息并不能解决我的问题。我想将消息存储在一个列表中,以便我可以维护这些消息的顺序(顺序在我的场景中是隐含的)。您的方法将首先列出所有错误消息,然后列出警告消息。你能帮我弄清楚我们可以使用一个列表与杰克逊一起实现这一目标吗?谢谢你的帮助。 jgen.writeObjectField(me.getType(), me);正在抛出 ArrayIndexOutofBounds 异常。能够通过使用 provider.defaultSerializeField(me.getType(), me, jgen); 进行修复; 使用您的代码,您仍然可以在 XML 中获取类型标记。上面的代码,使用@JsonIgnore 得到了你想要的结果吗? 是的。通过使用 provider.defaultSerializeField(me.getType(), me, jgen);我能够获得所需的格式。正如您提到的,使用 @JsonIgnore 注释忽略了类型字段。【参考方案4】:与我类似的问题。我编写了一个自定义 JsonSerializer 来为集合中的每个项目生成不同的 xml 元素名称(从 @JsonTypeName 读取)。
这是我的 JsonSerializer:
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
public class NamedCollectionXmlSerializer extends JsonSerializer<Collection<Object>>
@Override
public void serialize(Collection<Object> list, JsonGenerator gen, SerializerProvider provider) throws IOException
boolean toXml = gen instanceof ToXmlGenerator;
if (!toXml)
// fallback to the default behavior for non-xml serialization
gen.writeObject(list);
return;
gen.writeStartArray();
if (list != null)
for (Object item : list)
if (item == null)
continue;
JsonTypeName jsonTypeName;
if ((jsonTypeName = item.getClass().getAnnotation(JsonTypeName.class)) != null)
// read JsonTypeName as the xml element name
// if JsonTypeName not present, use the default name
((ToXmlGenerator) gen).setNextName(new QName("", jsonTypeName.value()));
gen.writeObject(item);
gen.writeEndArray();
对于以下 POJO(lombok 生成的 getter 和构造函数):
interface Message
@Getter
@RequiredArgsConstructor
class ResponsePOJO
@JacksonXmlElementWrapper(useWrapping = false)
@JsonSerialize(using = NamedCollectionXmlSerializer.class)
private final List<Message> messages;
@Getter
@RequiredArgsConstructor
@JsonTypeName("Error")
class Error implements Message
private final String msg;
@Getter
@RequiredArgsConstructor
@JsonTypeName("Warning")
class Warning implements Message
private final String msg;
和测试代码:
ResponsePOJO response = new ResponsePOJO(
Arrays.asList(new Error("error1"), new Warning("warn1"), new Error("error2"))
);
new XmlMapper().writerWithDefaultPrettyPrinter().writeValue(System.out, response);
这是输出:
<ResponsePOJO>
<Error>
<msg>error1</msg>
</Error>
<Warning>
<msg>warn1</msg>
</Warning>
<Error>
<msg>error2</msg>
</Error>
</ResponsePOJO>
PS:我用 jackson 2.9.3 版测试我的代码
【讨论】:
以上是关于Jackson 序列化:将字段值设置为 XML 元素名称的主要内容,如果未能解决你的问题,请参考以下文章
使用 Jackson 反序列化:获取 Json 对象设置的字段列表
Jackson 2.11.4:为缺少的 JSON 字段设置默认值
Jackson xml反序列化 - 序列化为一个列表,其中包含任意元素