有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java Jackson序列化:将字段值设置为XML元素名称

在基于jax-rs的restapi项目中,我们使用jacksonjax-rsxml内容提供者来处理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
}

默认序列化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序列化ResponseJo时,我们得到了以下异常

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

你能帮我解决这个问题吗


共 (4) 个答案

  1. # 1 楼答案

    我的意见是你的方法太复杂了。我将改为将您的消息类重组为:

    public class Message { private String msg; // The actual message },并根据类型对其进行子类化:

    public class Error extends Message { }

    public class Warning extends Message { }

    此外,这种方法允许您向每种类型添加自定义字段,这更灵活

  2. # 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> 
    

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

  3. # 3 楼答案

    我也有类似的问题。我编写了一个自定义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测试我的代码

  4. # 4 楼答案

    编辑到上一个解决方案: 您就快到了,只需要将@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>