Search code examples
javajsondeserializationjackson

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?


Solution

  • 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();