2012-01-31 22 views
13

Mój problem jest dość prosta: Mam następującą prostą klasę:Błąd deserializacji Jackson obsługi

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

I staram się przetwarzać następujące JSON:

{ 
    "id": "blah" 
} 

Oczywiście, nie jest to problem tutaj ("blah" nie może być przetwarzany na int)

Dawniej Jackson wyrzuca coś podobnego do org.codehaus.jackson.map.JsonMappingException: Nie można skonstruować wystąpienia java.lang.Integer z String value "blah": niepoprawna wartość Integer

Zgadzam się z tym, ale chciałbym zarejestrować coś gdzieś, co pozwoli zignorować tego typu błędy mapowania. Próbowałem z zarejestrowanym programem DeserializationProblemHandler (zobacz here), ale wydaje się, że działa on tylko na nieznanych właściwościach, a nie na problemach z deserializacją.

Masz jakieś wskazówki na ten temat?

+0

Dlaczego chcesz, aby zignorować ten błąd? Zwrócę kod HTTP '400' do każdego klienta, który spróbuje' PUT' mnie reprezentację zasobu w ten sposób :) –

+1

Używam Jackson z Spring MVC i sprawdzania fasoli. Problem polega na tym, że Jackson narzeka na problemy z deserializacją, zanim dotrę do warstwy wiosennej mvc .. więc nie mogę wysłać do mojego klienta błędów w spójny sposób. –

+0

Również ja (dla jednego) często używam Jacksona do zrobienia czytelnego zrzutu obiektu do dziennika. Możliwość odnotowania problemów z serializacją i poruszania się jest bardzo pomocna. –

Odpowiedz

9

Udało mi się rozwiązać mój problem, dzięki Tatu from Jackson ML.

Musiałem użyć niestandardowych nie blokujących deserializatorów dla każdego prymitywnego typu obsługiwanego w Jackson. Coś w tej fabryce:

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

Następnie nazywając ją niczym ten sposób (uchodzić deserializers tylko te, które chcą być non blokowanie):

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

może chcesz, aby kontroler poradzić sobie z problemem dodając metodę, która obsługuje ten szczególny wyjątek

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

oczywiście linię

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

może w niektórych przypadkach rzucić wyjątek klasy rzutu, ale jeszcze ich nie spotkałem.

+1

Masz rację, to może być prawidłowe rozwiązanie. Ma jednak pewne wady: - Będziesz mieć zgłoszone błędy jeden po drugim (będziesz potrzebować 3 próśb, aby zidentyfikować 3 źle sformatowane pola). Umieszczenie wartości pustej, gdy pole jest źle sformatowane, pozwala umieścić adnotację \ @NotNull na polu, aby mieć pełną listę błędów w 1 żądaniu. - Umożliwia to obsługę poszczególnych przypadków dla każdego pola.Jeśli niektóre pola są źle sformatowane * i * nie jest to krytyczne, nie umieszczam \ @NotNull na tych polach => błąd zostanie odfiltrowany –

+0

Tak, masz rację, twoje podejście jest lepsze. Jestem zainteresowany tym, jak sprawić, by działał na prymitywną wartość, ponieważ chcę, aby mój model domeny miał ** prywatny int ** ** zamiast ** prywatny wiek całkowity **. Udało mi się rozwiązać to za pomocą ** nowy StdDeserializer.IntegerDeserializer (int.class, 0), ** i zastępując ** return null ** z deserialize metoda z ** return delegate.getNullValue(); **. Jeśli uważasz, że to poprawne rozwiązanie, zaktualizuj swój kod, aby uwzględnić to również, jeśli masz inne podejście, udostępnij je. –

+0

Zastanawiam się, czy dobrze jest wziąć pod uwagę surowe typy w tym przypadku. Ponieważ wydaje mi się dziwne używanie niektórych wartości do reprezentowania "nielegalnej" wartości (w twoim przypadku wartość wydaje się wynosić 0). Wolałbym użyć mniej użytej wartości, takiej jak Integer.MAX_VALUE, ale nawet w tym przypadku uważam, że to zachowanie jest zbyt arbitralne (to jest osobiste POV). –

0

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

}

Powiązane problemy