2012-01-31 7 views
13

Il mio problema errore di deserializzazione è abbastanza semplice: Ho il seguente semplice classe:Jackson movimentazione

public class Foo { 
    private int id = -1; 
    public void setId(int _id){ this.id = _id; } 
    public int getId(){ return this.id; } 
} 

E io sto cercando di elaborare seguente JSON:

{ 
    "id": "blah" 
} 

Ovviamente, c'è un problema qui ("blah" non può essere analizzato ad un int)

Precedentemente, Jackson genera qualcosa come org.codehaus.jackson.map.JsonMappingException: impossibile creare l'istanza di java.lang.Integer da String valore 'blah': non un valore intero valido

Sono d'accordo con questo, ma mi piacerebbe registrare qualcosa da qualche parte permettendo di ignorare questo tipo di errori di mappatura. Ho provato con un DeserializationProblemHandler registrato (vedi here) ma sembra funzionare solo su proprietà sconosciute e non su problemi di deserializzazione.

Avete qualche indizio su questo problema?

+0

Perché vuoi ignorare questo errore? Mi piacerebbe restituire un codice HTTP di '400' per ogni client che prova a' PUT' una rappresentazione di risorse come questa :) –

+1

Sto usando Jackson con Spring MVC e convalida del bean. Il problema è che Jackson si sta lamentando dei problemi di deserializzazione, prima di raggiungere il livello mvc di primavera .. quindi non posso inviare al mio cliente gli errori in modo coerente. –

+0

Anche io (per uno) uso abbastanza spesso Jackson per fare un dump leggibile di un oggetto su un log. Essere in grado di notare i problemi di serializzazione e andare avanti è molto utile –

risposta

9

Sono riuscito a risolvere il mio problema, grazie a Tatu from Jackson ML.

Ho dovuto utilizzare deserializzatori personalizzati non bloccanti per tutti i tipi primitivi gestiti in Jackson. Qualcosa di simile a questa fabbrica:

public class JacksonNonBlockingObjectMapperFactory { 

    /** 
    * Deserializer that won't block if value parsing doesn't match with target type 
    * @param <T> Handled type 
    */ 
    private static class NonBlockingDeserializer<T> extends JsonDeserializer<T> { 
     private StdDeserializer<T> delegate; 

     public NonBlockingDeserializer(StdDeserializer<T> _delegate){ 
      this.delegate = _delegate; 
     } 

     @Override 
     public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { 
      try { 
       return delegate.deserialize(jp, ctxt); 
      }catch (JsonMappingException e){ 
       // If a JSON Mapping occurs, simply returning null instead of blocking things 
       return null; 
      } 
     } 
    } 

    private List<StdDeserializer> jsonDeserializers = new ArrayList<StdDeserializer>(); 

    public ObjectMapper createObjectMapper(){ 
     ObjectMapper objectMapper = new ObjectMapper(); 

     SimpleModule customJacksonModule = new SimpleModule("customJacksonModule", new Version(1, 0, 0, null)); 
     for(StdDeserializer jsonDeserializer : jsonDeserializers){ 
      // Wrapping given deserializers with NonBlockingDeserializer 
      customJacksonModule.addDeserializer(jsonDeserializer.getValueClass(), new NonBlockingDeserializer(jsonDeserializer)); 
     } 

     objectMapper.registerModule(customJacksonModule); 
     return objectMapper; 
    } 

    public JacksonNonBlockingObjectMapperFactory setJsonDeserializers(List<StdDeserializer> _jsonDeserializers){ 
     this.jsonDeserializers = _jsonDeserializers; 
     return this; 
    } 
} 

Poi chiamarlo in questo modo (passare come deserializzatori solo quelli che si desidera essere non bloccante):

JacksonNonBlockingObjectMapperFactory factory = new JacksonNonBlockingObjectMapperFactory(); 
factory.setJsonDeserializers(Arrays.asList(new StdDeserializer[]{ 
    // StdDeserializer, here, comes from Jackson (org.codehaus.jackson.map.deser.StdDeserializer) 
    new StdDeserializer.ShortDeserializer(Short.class, null), 
    new StdDeserializer.IntegerDeserializer(Integer.class, null), 
    new StdDeserializer.CharacterDeserializer(Character.class, null), 
    new StdDeserializer.LongDeserializer(Long.class, null), 
    new StdDeserializer.FloatDeserializer(Float.class, null), 
    new StdDeserializer.DoubleDeserializer(Double.class, null), 
    new StdDeserializer.NumberDeserializer(), 
    new StdDeserializer.BigDecimalDeserializer(), 
    new StdDeserializer.BigIntegerDeserializer(), 
    new StdDeserializer.CalendarDeserializer() 
})); 
ObjectMapper om = factory.createObjectMapper(); 
6

Si potrebbe desiderare di lasciare che il controller di gestire il problema con l'aggiunta di un metodo che gestisce questa eccezione specifica

@ExceptionHandler(HttpMessageNotReadableException.class) 
@ResponseBody 
public String handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) 
{ 
    JsonMappingException jme = (JsonMappingException) ex.getCause(); 
    return jme.getPath().get(0).getFieldName() + " invalid"; 
} 

Naturalmente, la linea

JsonMappingException jme = (JsonMappingException) ex.getCause(); 

potrebbe lanciare un'eccezione di classe per alcuni casi ma non li ho ancora incontrati.

+1

Hai ragione, questa potrebbe essere una soluzione valida. Tuttavia, ha alcuni inconvenienti: - Avrete errori segnalati uno ad uno (avrete bisogno di 3 richieste per identificare che avete 3 campi formattati male). Inserire il valore nullo quando il campo è formattato male consente di inserire un'annotazione \ @NotNull sul campo per avere un elenco completo di errori in 1 richiesta - Ciò consente una gestione caso per caso su ciascun campo.Se alcuni campi sono formattati male * e * questo non è critico, non inserisco un \ @NotNull in questi campi => l'errore verrà filtrato –

+0

Sì, hai ragione, il tuo approccio è migliore. Sono interessato a come lo si fa funzionare per un valore primitivo, perché voglio che il mio modello di dominio abbia un ** int int privato **, anziché ** Integer privato **. Sono riuscito a risolvere questo problema usando ** nuovo StdDeserializer.IntegerDeserializer (int.class, 0), ** e sostituendo ** return null ** dal metodo deserialize con ** return delegate.getNullValue(); **. Se ritieni che questa sia la soluzione corretta, ti preghiamo di aggiornare il tuo codice per includere anche questo, se hai un altro approccio, per favore condividi. –

+0

Mi chiedo se sia una buona idea considerare i tipi non elaborati in quel caso. Perché mi sembra strano usare alcuni valori per rappresentare un valore "illegale" (nel tuo caso, il valore sembra essere 0). Preferisco usare un valore meno utilizzato come Integer.MAX_VALUE, ma anche in questo caso, trovo questo comportamento un po 'troppo arbitrario (questo è un POV personale) –

0

creare un semplice Mapper:

@Provider 
@Produces(MediaType.APPLICATION_JSON) 
public class JSONProcessingErroMapper implements ExceptionMapper<InvalidFormatException> { 

@Override 
public Response toResponse(InvalidFormatException ex) { 
    return Response.status(400) 
      .entity(new ClientError("[User friendly message]")) 
      .type(MediaType.APPLICATION_JSON) 
      .build(); 
} 

}