We are using Jackson jax-rs XML content providers for handling XML content type in our jax-rs based REST API project. In the serializing a List of POJOs, we need to set the xml element name dynamically from a field in the POJO.
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
}
Default Jackson serialized 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>
Our requirement, ie., set type as the XML element name.
<ResponsePOJO>
<Error>
<msg>Some random error message</msg>
</Error>
<Warning>
<msg>Some random warning message</msg>
</Warning>
</ResponsePOJO>
In order to achieve this, we wrote a custom XML serializer in the following manner:
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);
}
}
}
And added the serializer using annotation:
@JacksonXmlProperty
@JacksonXmlElementWrapper(useWrapping = false)
@JsonSerialize(using=MessageListSerializer.class)
private List<Message> message = new ArrayList<Message>();
But while serializing the ResponsePOJO using Jackson XMLMapper, we are getting the following exception...
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
Could you please help me resolve this issue...
Jackson is a library for handling JSON in Java systems and now has support for XML from version 2. DOM4J is a memory-efficient library for parsing XML, XPath, and XSLT (eXtensible Stylesheet Language).
Reading XMLWe can also read XML, using the various readValue APIs that are part of provided by the ObjectMapper. For example, reading some XML from an InputStream into a Java Bean: MyBean bean = objectMapper.
Package. Description. com.fasterxml.jackson.dataformat.xml. Package that contains XML-based backends which can serialize POJOs to and deserialize from XML, using Stax XML parsers and generators for XML processing and mostly standard Jackson data binding otherwise.
Edit to previous solution:
You're nearly there, just need to add @JsonIgnore
to private String type; // "Error" or "Warning"
<ResponsePOJO>
<Error>
<msg>error message</msg>
</Error>
<Warning>
<msg>warning message</msg>
</Warning>
</ResponsePOJO>
The following will output the above 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;
}
}
}
along with the class
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);
}
}
}
with dependencies
<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>
"I cannot post it in comment since it is too long" The following is the customized classes:
@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 {
}
I tested it with my demo code:
MyResponse myResponse = new MyResponse();
MyMessage error = new MyError();
error.setText("error");
MyMessage warning = new MyWarning();
warning.setText("warning");
myResponse.setMessages(Lists.newArrayList(error, warning));
and it returned:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myResponse>
<messages>
<error>
<text>error</text>
</error>
<warning>
<text>warning</text>
</warning>
</messages>
</myResponse>
You need to tweak element name to get desired results though.
Similar problem to me. I wrote a custom JsonSerializer to generate different xml element name (read from @JsonTypeName) for each item in a collection.
Here is my 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();
}
}
for the following POJO (getters and constructors generated by lombok):
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;
}
and test code:
ResponsePOJO response = new ResponsePOJO(
Arrays.asList(new Error("error1"), new Warning("warn1"), new Error("error2"))
);
new XmlMapper().writerWithDefaultPrettyPrinter().writeValue(System.out, response);
this is output:
<ResponsePOJO>
<Error>
<msg>error1</msg>
</Error>
<Warning>
<msg>warn1</msg>
</Warning>
<Error>
<msg>error2</msg>
</Error>
</ResponsePOJO>
PS: I test my code with jackson version 2.9.3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With