2013-02-12 12 views
10

Supponiamo di avere le seguenti tre classi (getter e setter omesso per brevità):Utilizzando Jackson JSON Generator, come posso scrivere più oggetti su un campo?

@JsonAutoDetect 
public class InfoCollection{ 
    private InfoType1 info1; 
    private InfoType2 info2; 
} 

@JsonAutoDetect 
public class InfoType1{ 
    private String fieldA; 
} 

@JsonAutoDetect 
public class InfoType2{ 
    private String fieldB; 
} 

I "m cercando di scrivere una funzione JsonSerializer.serialize() che serializza un oggetto InfoCollection in questo formato:

{ 
    "allInfo":{ 
     "fieldA":"foo", 
     "fieldB":"bar" 
    } 
} 

Questo è quello che ho adesso:

jsonGenerator.writeStartObject(); 
jsonGenerator.writeFieldName("allInfo"); 
jsonGenerator.writeObject(myInfoCollection.getInfo1()); 
jsonGenerator.writeObject(myInfoCollection.getInfo2()); 
jsonGenerator.writeEndObject(); 

che sta causando la seguente eccezione:

org.codehaus.jackson.JsonGenerationException: Can not start an object, expecting field name 

Mi manca qualcosa di piccolo o sto andando totalmente in questo modo nel modo sbagliato?

NOTA: Un paio di soluzioni proposte finora coinvolge la scrittura di ogni singolo campo di InfoType1 e InfoType2. Sto cercando una soluzione che non richiede questo perché mi piacerebbe utilizzare la soluzione su classi enormi con molti campi.

risposta

3

In futuro, quando si dispone di una traccia di stack, facci sapere in quale riga si presenta il problema.

Detto questo, la correzione è probabilmente:

jsonGenerator.writeStartObject(); 
jsonGenerator.writeFieldName("allInfo"); 

jsonGenerator.writeStartObject(); // start nested object 
jsonGenerator.writeFieldName("fieldA"); // start field 
jsonGenerator.writeObject(myInfoCollection.getInfo1().fieldA); 

jsonGenerator.writeFieldName("fieldB"); // start fieldB 
jsonGenerator.writeObject(myInfoCollection.getInfo2().fieldB); 

jsonGenerator.writeEndObject(); // end nested object 

jsonGenerator.writeEndObject(); 

soluzione con un oggetto wrapper:

@JsonAutoDetect 
public class Wrapper { 
    private transient InfoCollection data; // transient makes Jackson ignore this 

    public String getFieldA() { return data.info1.fieldA; } 
    public String getFieldB() { return data.info1.fieldB; } 
} 

che rende Jackson vedere solo quello che vuoi e come vuoi.

In alternativa, l'uso di riflessione per raccogliere in modo ricorsivo tutti i campi ed i loro nomi:

List<Pair<String, Object>> data = collectFields(myInfoCollection); 

collectFields dovrebbe esaminare tutti i campi e aggiungere tutto per la lista che è o una primitiva o, diciamo, dove field.getType().getName().startsWith("java.lang") o qualsiasi altra regola hai bisogno.

Se il campo è un riferimento, chiamare collectFields() in modo ricorsivo.

Quando si dispone dell'elenco, è sufficiente chiamare jsonGenerator in un ciclo per scrivere i risultati.

+0

Questo metodo probabilmente funzionerebbe, ma mi ha scritto ogni singolo campo. Sto cercando di trovare una soluzione che possa essere applicata a classi più grandi che hanno molti più campi. –

+0

Utilizzare la riflessione e le annotazioni per individuare i campi per scrivere e creare il generatore da un paio di metodi/classi di supporto che consentono di riutilizzare il codice. –

+0

In alternativa, crea una classe wrapper che ti fornisce un'API più vicina all'output che Jackson dovrebbe creare. –

8

Invece di chiamare writeFieldName("allInfo") è necessario chiamare writeObjectFieldStart("allInfo") perché "allInfo" è un altro oggetto JSON. Così il vostro serializzatore personalizzato dovrebbe apparire come segue:

public void serialize(InfoCollection infoCollection, JsonGenerator jgen, SerializerProvider provider) throws IOException{ 
    jgen.writeStartObject(); 
    jgen.writeObjectFieldStart("allInfo"); 
    jgen.writeObjectField("fieldA", infoCollection.getInfo1().getFieldA()); 
    jgen.writeObjectField("fieldB", infoCollection.getInfo2().getFieldB()); 
    jgen.writeEndObject(); 
    jgen.writeEndObject(); 
} 

Oppure si può tentare l'approccio basato nota: (. È necessario abilitare SerializationConfig.Feature.WRAP_ROOT_VALUE funzione in modo che questo funzioni vedere Serialization features)

@JsonRootName("allInfo") 
public class InfoCollection { 
    @JsonUnwrapped 
    private InfoType1 info1; 
    @JsonUnwrapped 
    private InfoType2 info2; 

    /* getters, setters */ 
} 

+0

Questo metodo probabilmente funzionerebbe, ma mi ha scritto ogni singolo campo. Sto cercando di trovare una soluzione che possa essere applicata a classi più grandi che hanno molti più campi. –

+0

Puoi farlo anche con le annotazioni, ma non è così flessibile come serializzatori personalizzati. Ho aggiornato la risposta con l'esempio basato sull'annotazione. – wajda