Search code examples
javajsonexceptionjacksonjackson-databind

Jackson customized deserialize runtime exception been wrapped with JsonMappingException


I have a customised deserialiser, expect CustomRuntimeException when id is not String type, but the exception was wrapped with JsonMappingException in my test result. The test case works fine before I upgrade the jackson-databind module from 2.5.x to 2.10.x. But it failed with:

java.lang.Exception: Unexpected exception, expected <CustomRuntimeException> but was <com.fasterxml.jackson.databind.JsonMappingException>

after I upgraded the jackson-databind.

@Override
public Optional<String> deserialize(JsonParser jsonParser,
                                    DeserializationContext deserializationContext) throws IOException {

  if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
    throw new CustomRuntimeException("Id should be String");
  }
  return Optional.ofNullable(jsonParser.getValueAsString());
}

The CustomRuntimeException:

public class CustomRuntimeException extends RuntimeException {

    public CustomRuntimeException() {
        super("Invalid argument is provided.");
    }

    public CustomRuntimeException(String message) {
        super(message);
    }
}

Test case:

@Test(expected = CustomRuntimeException.class)
public void shouldThrowCustomRuntimeException() throws IOException {
  objectMapper.readValue("{ \"id\": 1245672564 }", IdSchema.class);
}

Stack trace:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: id should be String (through reference chain: com.xxx["id"])
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:397)
    at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:356)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1714)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:530)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:417)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1287)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3214)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3182)
    at com.xxx.shouldThrowCustomRuntimeException(xxx.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19)
    ... 16 more
Caused by: com.xxx.CustomRuntimeException: id should be String

Solution

  • You can disable feature DeserializationFeature.WRAP_EXCEPTIONS:

    Feature that determines whether Jackson code should catch and wrap Exceptions (but never Errors!) to add additional information about location (within input) of problem or not. If enabled, most exceptions will be caught and re-thrown (exception specifically being that IOExceptions may be passed as is, since they are declared as throwable); this can be convenient both in that all exceptions will be checked and declared, and so there is more contextual information. However, sometimes calling application may just want "raw" unchecked exceptions passed as is. Feature is enabled by default.

    Example:

    ObjectMapper mapper = JsonMapper.builder()
            .disable(DeserializationFeature.WRAP_EXCEPTIONS)
            .build();
    

    Or, you can extend com.fasterxml.jackson.databind.JsonMappingException instead of RuntimeException:

    class CustomRuntimeException extends JsonMappingException {
    
        public CustomRuntimeException(Closeable processor, String msg) {
            super(processor, msg);
        }
    
        public CustomRuntimeException(Closeable processor) {
            super(processor, "Invalid argument is provided.");
        }
    }
    

    and throw it this way from deserialiser:

    if (p.getCurrentToken() != JsonToken.VALUE_STRING) {
        throw new CustomRuntimeException(p, "Id should be String");
    }
    

    In both cases you should see something similar to:

    Exception in thread "main" com.celoxity.CustomRuntimeException: Id should be String
     at [Source: (File); line: 1, column: 9] (through reference chain: com.example.IdSchema["id"])
        at com.example.IdJsonDeserializer.deserialize(JsonTypeInfoApp.java:36)
        at com.example.IdJsonDeserializer.deserialize(JsonTypeInfoApp.java:31)
        at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3070)