2015-04-08 10 views
5

Utilizziamo i provider di contenuti XML Jackson jax-rs per la gestione del tipo di contenuto XML nel nostro progetto REST API basato su jax-rs. Nella serializzazione di un elenco di POJO, è necessario impostare dinamicamente il nome dell'elemento xml da un campo nel POJO.Serializzazione Jackson: impostazione del valore del campo come nome dell'elemento 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 
} 

predefinito Jackson XML serializzato:.

<ResponsePOJO> 
    <message> 
     <type>Error</type> 
     <msg>Some random error message</msg> 
    </message> 
    <message> 
     <type>Warning</type> 
     <msg>Some random warning message</msg> 
    </message> 
</ResponsePOJO> 

Il nostro fabbisogno, vale a dire, il tipo impostato come il nome dell'elemento XML.

<ResponsePOJO> 
    <Error> 
     <msg>Some random error message</msg> 
    </Error> 
    <Warning> 
     <msg>Some random warning message</msg> 
    </Warning> 
</ResponsePOJO> 

Al fine di raggiungere questo obiettivo, abbiamo scritto un serializzatore XML personalizzato, nel modo seguente:

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); 
     } 
    } 
} 

e ha aggiunto il serializzatore con annotazione:

@JacksonXmlProperty 
@JacksonXmlElementWrapper(useWrapping = false) 
@JsonSerialize(using=MessageListSerializer.class) 
private List<Message> message = new ArrayList<Message>(); 

Ma mentre la serializzazione del ResponsePOJO usando Jackson XMLMapper, stiamo ottenendo la seguente eccezione ...

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 

La prego di aiutarmi a risolvere questo problema ...

risposta

0

La mia opinione è che il tuo approccio è troppo complicato. Vorrei invece ristrutturare la vostra classe di messaggio come:

public class Message { private String msg; // The actual message } e sottoclasse è basata su tipi:

public class Error extends Message { }

public class Warning extends Message { }.

Inoltre, questo approccio consente di aggiungere campi personalizzati per ogni tipo, che è più flessibile.

+1

Grazie per la risposta. Anche in questo caso, come possiamo impostare "Errore" e "Avviso" come nome dell'elemento XML, senza creare elenchi separati per i messaggi "Errore" e "Avviso". – dinup24

+0

Per essere più specifici, ho bisogno di mantenere l'ordine di questi messaggi e quindi utilizzare una lista singola Lista per memorizzare sia oggetti di errore che di avviso. In questo caso, è possibile impostare il nome della sottoclasse come nome dell'elemento XML. – dinup24

0

"Non riesco a postare in un commento dal momento che è troppo lungo" Quello che segue è le classi personalizzate:

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

} 

ho provato con il mio codice demo:

MyResponse myResponse = new MyResponse(); 
MyMessage error = new MyError(); 
error.setText("error"); 

MyMessage warning = new MyWarning(); 
warning.setText("warning"); 
myResponse.setMessages(Lists.newArrayList(error, warning)); 

ed è tornato :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<myResponse> 
    <messages> 
    <error> 
     <text>error</text> 
    </error> 
    <warning> 
     <text>warning</text> 
    </warning> 
    </messages> 
</myResponse> 

È necessario modificare nome dell'elemento t o ottenere risultati desiderati però.

+1

rimuovere @XmlElementWrapper se non si desidera vedere ABOS

+0

Grazie per la risposta. Funziona meravigliosamente bene con il marshaller JAXB. Rimosso @XmlElementWrapper in base alle mie esigenze. Abbiamo un equivalente Jackson. – dinup24

1

Modifica per soluzione precedente: Stai quasi arrivati, basta aggiungere @JsonIgnore a private String type; // "Error" or "Warning"

<ResponsePOJO> 
    <Error> 
     <msg>error message</msg> 
    </Error> 
    <Warning> 
     <msg>warning message</msg> 
    </Warning> 
</ResponsePOJO> 

Il seguente output volontà di XML qui sopra:

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; 
     } 
    } 


} 

insieme alla classe

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); 
     } 
    } 


} 

con dipendenze

<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> 
+0

La creazione di più elenchi per memorizzare messaggi di errore e avviso non risolve il mio problema. Vorrei memorizzare i messaggi in un'unica lista in modo da poter mantenere l'ordine di questi messaggi (l'ordine è impt nel mio scenario). Ci si avvicina a elencare prima tutti i messaggi di errore e poi i messaggi di avviso. Puoi aiutarmi a capire chi possiamo ottenere questo con Jackson usando una sola lista .. Grazie per il tuo aiuto. – dinup24

+0

Ho aggiornato la soluzione – pandemicGander

+1

jgen.writeObjectField (me.getType(), me); sta lanciando l'eccezione ArrayIndexOutofBounds. È stato in grado di risolvere utilizzando provider.defaultSerializeField (me.getType(), me, jgen); – dinup24

0

Problema simile a me. Ho scritto un JsonSerializer personalizzato per generare un nome di elemento xml diverso (leggi da @JsonTypeName) per ogni elemento di una raccolta.

Ecco il mio 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(); 
    } 
} 

per le seguenti POJO (getter e costruttori generati da Lombok): codice

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; 

} 

e test:

ResponsePOJO response = new ResponsePOJO(
     Arrays.asList(new Error("error1"), new Warning("warn1"), new Error("error2")) 
); 
new XmlMapper().writerWithDefaultPrettyPrinter().writeValue(System.out, response); 

questo è uscita :

<ResponsePOJO> 
    <Error> 
    <msg>error1</msg> 
    </Error> 
    <Warning> 
    <msg>warn1</msg> 
    </Warning> 
    <Error> 
    <msg>error2</msg> 
    </Error> 
</ResponsePOJO> 

PS: I test il mio codice con la versione 2.9.3 Jackson