2012-01-31 35 views
13

Mi problema es bastante simple: Tengo la siguiente clase simple:Jackson error de deserialización manejo

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

Y yo estoy tratando de procesar siguiente JSON:

{ 
    "id": "blah" 
} 

Obviamente, hay un problema aquí ("bla" no se puede analizar en un int)

Anteriormente, Jackson arroja algo así como org.codehaus.jackson.map.JsonMappingException: No se puede construir instancia de java.lang.Integer desde String valor 'blah': no ​​es un valor entero válido

Estoy de acuerdo con esto, pero me gustaría registrar algo en algún lugar que permita ignorar este tipo de errores de mapeo. Intenté con un DeserializationProblemHandler registrado (vea here) pero parece que solo funciona en propiedades desconocidas y no en problemas de deserialización.

¿Tiene alguna pista sobre este tema?

+0

¿Por qué quiere hacer caso omiso de este error? Devolvería un código HTTP de '400' a cada cliente que intenta' PON' ponerme una representación de recursos como esta :) –

+1

Estoy usando Jackson con Spring MVC y validación de beans. El problema es que Jackson se queja de problemas de deserialización, antes de llegar a la capa mvc de primavera ... por lo que no puedo enviar a mi cliente los errores de forma consistente. –

+0

También utilizo (a menudo) a Jackson para hacer un volcado legible de un objeto a un registro. Ser capaz de notar los problemas de serialización y seguir adelante es muy útil –

Respuesta

9

Pude resolver mi problema, gracias a Tatu from Jackson ML.

Tuve que usar deserializadores personalizados sin bloqueo para cada uno de los tipos primitivos manejados en Jackson. Algo parecido a esta fábrica:

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

Luego calificó como de esta manera (pasar como deserializadores sólo aquellos que desea ser no bloqueante):

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

Es posible que desee dejar que su controlador de manejar el problema añadiendo un método que controla esta excepción específica

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

por supuesto, la línea

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

podría arrojar una excepción de lanzamiento de clase para algunos casos, pero aún no los he encontrado.

+1

Tiene razón, esta podría ser una solución válida. Sin embargo, tiene algunos inconvenientes: - Tendrá errores informados uno por uno (necesitará 3 solicitudes para identificar que tiene 3 campos mal formateados). Poner valor nulo cuando el campo está mal formateado me permite poner una anotación \ @NotNull en el campo para tener una lista exhaustiva de errores en 1 solicitud - Esto permite un manejo caso por caso en cada campo.Si algunos campos están mal formateados * y * esto no es crítico, no coloque un \ @NotNull en estos campos => se filtrará el error –

+0

Sí, tiene razón, su enfoque es mejor. Me interesa cómo hacer que funcione para un valor primitivo, porque quiero que mi modelo de dominio tenga ** int privado **, en lugar de ** edad entera privada **. Pude resolver esto usando ** new StdDeserializer.IntegerDeserializer (int.class, 0), ** y reemplazando el ** return null ** del método deserialize con ** return delegate.getNullValue(); **. Si crees que esta es la solución correcta, actualiza tu código para incluir esto también, si tienes otro enfoque, por favor comparte. –

+0

Me pregunto si es una buena idea considerar tipos crudos en ese caso. Porque me parece extraño usar algunos valores para representar un valor "ilegal" (en su caso, el valor parece ser 0). Prefiero usar un valor menos utilizado como Integer.MAX_VALUE, pero incluso en ese caso, considero que este comportamiento es un poco arbitrario (esto es un POV personal) –

0

Crear una Mapper simple:

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

}

Cuestiones relacionadas