Jackson 自定义反序列化运行时异常被 JsonMappingException 包裹

Jackson customized deserialize runtime exception been wrapped with JsonMappingException

我有一个自定义的反序列化器,当 id 不是 String 类型时,期望 CustomRuntimeException,但在我的测试结果中异常被 JsonMappingException 包裹。在我将 jackson-databind 模块从 2.5.x 升级到 2.10.x 之前,测试用例工作正常。但它失败了:

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

我升级后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());
}

CustomRuntimeException:

public class CustomRuntimeException extends RuntimeException {

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

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

测试用例:

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

堆栈跟踪:

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.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

您可以禁用功能 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.

示例:

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

或者,您可以扩展 com.fasterxml.jackson.databind.JsonMappingException 而不是 RuntimeException

class CustomRuntimeException extends JsonMappingException {

    public CustomRuntimeException(Closeable processor, String msg) {
        super(processor, msg);
    }

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

并从反序列化器中以这种方式抛出:

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

在这两种情况下,您应该会看到类似于以下内容的内容:

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)