Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson deserialization error handling

My problem is fairly simple : I have the following simple class:

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

And I am trying to process following JSON:

{
  "id": "blah"
}

Obviously, there is a problem here ("blah" cannot be parsed to an int)

Formerly, Jackson throws something like org.codehaus.jackson.map.JsonMappingException: Can not construct instance of java.lang.Integer from String value 'blah': not a valid Integer value

I agree with this, but I'd like to register something somewhere allowing to ignore this type of mapping errors. I tried with a DeserializationProblemHandler registered (see here) but it seems to only work on unknown properties and not deserialization problems.

Have you any clue on this issue?

like image 924
Frédéric Camblor Avatar asked Jan 31 '12 14:01

Frédéric Camblor


People also ask

What is JsonMappingException?

public class JsonMappingException extends JsonProcessingException. Checked exception used to signal fatal problems with mapping of content, distinct from low-level I/O problems (signaled using simple IOException s) or data encoding/decoding problems (signaled with JsonParseException , JsonGenerationException ).

Does Jackson need default constructor?

Jackson uses default (no argument) constructor to create object and then sets value using setters. so you only need @NoArgsConstructor and @Setter.

What is @JsonDeserialize?

@JsonDeserialize is used to specify custom deserializer to unmarshall the json object.


2 Answers

I succeeded to solve my problem, thanks to Tatu from Jackson ML.

I had to use custom non blocking deserializers for every primitive types handled in Jackson. Something like this factory :

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

Then calling it like this way (pass as deserializers only those you want to be non blocking) :

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();
like image 141
Frédéric Camblor Avatar answered Sep 28 '22 17:09

Frédéric Camblor


You might want to let your controller handle the problem by adding a method that handles this specific exception

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

Of course, the line

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

might throw a class cast exception for some cases but i haven't encountered them yet.

like image 11
E L Avatar answered Sep 28 '22 18:09

E L