2011-01-22 16 views
7

Ho un servizio Web che restituisce un elenco come JSON. Usa Jackson per mappare un elenco di POJO Java in JSON. Il problema è che la rappresentazione JSON ha un oggetto wrapper attorno all'array e io voglio solo l'array. Vale a dire, sto ottenendo questo:Elimina oggetto wrapper durante la serializzazione di oggetti Java in JSON utilizzando Jackson

{"optionDtoList":[{...}, ..., {...}]} 

quando quello che voglio veramente è questo:

[{...}, ..., {...}] 

sto serializzazione direttamente la Lista Java; Non sto avvolgendo la lista con un oggetto wrapper e serializzando un oggetto wrapper. È Jackson che sembra aggiungere l'oggetto wrapper JavaScript.

Presumo che ci siano alcune annotazioni che posso usare sul POJO per sopprimere l'oggetto wrapper ma non lo vedo.

Vincoli sulla soluzione

mi piacerebbe risolvere questo problema sul lato servizio piuttosto che staccava l'involucro sul client. Il client è un widget dell'interfaccia utente jQuery (il widget di completamento automatico, non che importi) che si aspetta un array semplice e non voglio modificare il widget stesso.

Quello che ho provato

  • ho provato sostituisce l'elenco dei POJO Java con una serie di POJO Java e il risultato è lo stesso.
  • Ho provato @JsonTypeInfo(use = Id.NONE) pensando che questo potrebbe sopprimere il wrapper, ma non è così.
+1

Sembra come se si stesse serializzando l'oggetto sbagliato. Perché non puoi semplicemente usare il Mapper per serializzare la lista direttamente invece di serializzare il POJO circostante? –

+0

Sto serializzando la lista direttamente. I POJO sono nella lista. Il problema è che quando serializzo la lista, Jackson aggiunge un wrapper attorno ad esso. Stavo pensando che forse Jackson stesse distinguendo l'oggetto list dal suo array di supporto, così ho persino provato a serializzare direttamente un array. Senza fortuna; Jackson aggiunge ancora l'oggetto wrapper. –

+0

Puoi mostrarci il codice? Uso Jackson abbastanza frequentemente e non ho mai provato quello che stai descrivendo. –

risposta

5

In una modalità di test quando corro:

org.codehaus.jackson.map.ObjectMapper mapper = new org.codehaus.jackson.map.ObjectMapper(); 
String json = mapper.writeValueAsString(Arrays.asList("one","two","three","four","five")); 
System.out.println(json); 

rendimenti:

["one","two","three","four","five"] 

che è il comportamento che ti aspetti giusto?

Ho potuto vedere che quando restituisco questa lista tramite un controller Spring e lasciare che MappingJacksonJsonView gestisca la trasformazione dell'elenco in un json, allora sì c'è un wrapper attorno ad esso, che mi dice che il MappingJacksonJsonView è quello che aggiunge il wrapper. Una soluzione quindi potrebbe essere quella di tornare in modo esplicito il JSON dal controller, dire:

@RequestMapping(value = "/listnowrapper") 
public @ResponseBody String listNoWrapper() throws Exception{  
    ObjectMapper mapper = new ObjectMapper(); 
    return mapper.writeValueAsString(Arrays.asList("one","two","three","four","five")); 
} 
+0

Questo è esattamente ciò a cui alludevo. Serializzare la matrice invece di un oggetto che contiene una matrice. +1 – jmort253

+0

Grazie ragazzi. Questo deve essere ciò che sta succedendo. Sto davvero usando il MappingJacksonJsonView e ho semplicemente pensato che Jackson fosse il giocatore incriminato qui. :-) Apprezzo l'aiuto. –

0

Onestamente, non vorrei essere troppo veloce per cercare di correzione questo problema come avere l'involucro non creare una situazione in cui il codice è più estensibile. Se in futuro dovessi espanderlo per restituire altri oggetti, i tuoi clienti che consumano questo servizio web molto probabilmente non avranno bisogno di cambiare l'implementazione.

Tuttavia, se tutti i client prevedono un array non denominato, l'aggiunta di ulteriori proprietà in futuro al di fuori di tale array potrebbe interrompere l'interfaccia uniforme.

Con ciò detto, tutti hanno le loro ragioni per voler fare qualcosa in un certo modo. Come appare l'oggetto che stai serializzando? Stai serializzando un oggetto che contiene una matrice o stai serializzando la matrice vera e propria? Se il tuo POJO contiene un array, allora la soluzione è quella di estrarre l'array dal POJO e serializzare l'array.

+0

Sì, la mia ragione è che il contratto non è in palio: il componente client si aspetta un array raw. Sto serializzando la matrice stessa. Jackson aggiunge fastidiosamente l'involucro. –

1

Si potrebbe scrivere serializzatore personalizzato:

public class UnwrappingSerializer extends JsonSerializer<Object> 
{ 
    @Override 
    public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) 
      throws IOException, JsonProcessingException 
    { 
     JavaType type = TypeFactory.type(value.getClass()); 
     getBeanSerializer(type, provider).serialize(value, new UnwrappingJsonGenerator(jgen), provider); 
    } 

    private synchronized JsonSerializer<Object> getBeanSerializer(JavaType type, SerializerProvider provider) 
    { 
     JsonSerializer<Object> result = cache.get(type); 
     if (result == null) { 
      BasicBeanDescription beanDesc = provider.getConfig().introspect(type); 
      result = BeanSerializerFactory.instance.findBeanSerializer(type, provider.getConfig(), beanDesc); 
      cache.put(type, result); 
     } 
     return result; 
    } 

    private Map<JavaType, JsonSerializer<Object>> cache = new HashMap<JavaType, JsonSerializer<Object>>(); 

    private static class UnwrappingJsonGenerator extends JsonGeneratorDelegate 
    { 
     UnwrappingJsonGenerator(JsonGenerator d) 
     { 
      super(d); 
     } 

     @Override 
     public void writeEndObject() throws IOException, JsonGenerationException 
     { 
      if (depth-- >= yieldDepth) { 
       super.writeEndObject(); 
      } 
     } 

     @Override 
     public void writeFieldName(SerializedString name) throws IOException, JsonGenerationException 
     { 
      if (depth >= yieldDepth) { 
       super.writeFieldName(name); 
      } 
     } 

     @Override 
     public void writeFieldName(String name) throws IOException, JsonGenerationException 
     { 
      if (depth >= yieldDepth) { 
       super.writeFieldName(name); 
      } 
     } 

     @Override 
     public void writeStartObject() throws IOException, JsonGenerationException 
     { 
      if (++depth >= yieldDepth) { 
       super.writeStartObject(); 
      } 
     } 

     private int depth; 
     private final int yieldDepth = 2; 
    } 
} 

ignorerà gli oggetti esterni sulla profondità inferiore a quella indicata (2 di default).

Quindi utilizzare come segue:

public class UnwrappingSerializerTest 
{ 
    public static class BaseT1 
    { 
     public List<String> getTest() 
     { 
      return test; 
     } 

     public void setTest(List<String> test) 
     { 
      this.test = test; 
     } 

     private List<String> test; 
    } 

    @JsonSerialize(using = UnwrappingSerializer.class) 
    public static class T1 extends BaseT1 
    { 
    } 

    @JsonSerialize(using = UnwrappingSerializer.class) 
    public static class T2 
    { 
     public BaseT1 getT1() 
     { 
      return t1; 
     } 

     public void setT1(BaseT1 t1) 
     { 
      this.t1 = t1; 
     } 

     private BaseT1 t1; 
    } 

    @Test 
    public void test() throws IOException 
    { 
     ObjectMapper om = new ObjectMapper(); 
     T1 t1 = new T1(); 
     t1.setTest(Arrays.asList("foo", "bar")); 
     assertEquals("[\"foo\",\"bar\"]", om.writeValueAsString(t1)); 

     BaseT1 baseT1 = new BaseT1(); 
     baseT1.setTest(Arrays.asList("foo", "bar")); 
     T2 t2 = new T2(); 
     t2.setT1(baseT1); 
     assertEquals("{\"test\":[\"foo\",\"bar\"]}", om.writeValueAsString(t2)); 
    } 
} 

Note:

  • si aspetta unico campo unico involucro e genererà invalido JSON su qualcosa come {{field1: {...}, field2: {...}}
  • Se si utilizza personalizzato SerializerFactory probabilmente sarà necessario passarlo al serializzatore.
  • Utilizza la cache di serializzatore separata, quindi anche questo può essere un problema.
+0

OK, darò un'occhiata a quello. Spero comunque che ci sia qualcosa di più semplice: l'approccio serializzatore personalizzato sembra essere più utile rispetto alla generazione del JSON a mano. (Anche se possiamo diffondere il costo mentre implementiamo più mappature.) –

5

ottengo lo stesso problema come te.

Dopo aver aggiunto @ResponseBody davanti alla mia lista nella dichiarazione del metodo, il problema è stato risolto.

esempio:

public @ResponseBody List<MyObject> getObject

+0

Grazie per quello ... hai risolto il mio problema! – Paul

0

sono incappato in questo problema durante il tentativo di risolvere lo stesso problema, ma non è stato di utilizzare questo con un metodo @ResponseBody, ma era ancora incontrando il "wrapper" nel mio serializzato JSON. La mia soluzione era di aggiungere @JsonAnyGetter al metodo/campo, e quindi il wrapper sarebbe scomparso dal JSON.

Apparentemente si tratta di un bug di Jackson noto/soluzione alternativa: http://jira.codehaus.org/browse/JACKSON-765.

Problemi correlati