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 来存储错误和警告对象。在这种情况下,是否可以将子类名称设置为 XML 元素名称。【参考方案2】:

“我无法在评论中发布它,因为它太长了” 以下是自定义类:

 @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> 

您需要调整元素名称以获得所需的结果。

【讨论】:

如果您不想看到 ,请删除 @XmlElementWrapper 感谢您的回答。与 JAXB 编组器配合得非常好。删除了 @XmlElementWrapper 以满足我的要求。我们有杰克逊等价物吗?【参考方案3】:

编辑到以前的解决方案: 快到了,只需将@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忽略空字段

使用 Jackson 反序列化:获取 Json 对象设置的字段列表

Jackson 2.11.4:为缺少的 JSON 字段设置默认值

Jackson xml反序列化 - 序列化为一个列表,其中包含任意元素

使用 Jackson 从 XML 到 POJO 的反序列化问题:没有从字符串值反序列化的字符串参数构造函数/工厂方法

如何在全球范围内配置带弹簧的jackson?