diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java index 75ee3eebd4..b58d059bb2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java @@ -661,7 +661,12 @@ public enum MapperFeature implements ConfigFeature * * @since 2.19 */ - REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS(true) + REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS(true), + + /** + * @since 2.19 + */ + UNWRAP_ROOT_CAUSE(true) ; private final boolean _defaultState; diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java index 5c02e63bb2..20ba557cb8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java @@ -611,6 +611,32 @@ public SettableBeanProperty unwrapped(NameTransformer xf) /********************************************************** */ + /** + * Method that takes in exception of any type, and casts or wraps it + * to an IOException or its subclass. + */ + protected void _throwAsIOE(DeserializationConfig config, JsonParser p, Exception e, Object value) throws IOException + { + if (e instanceof IllegalArgumentException) { + String actType = ClassUtil.classNameOf(value); + StringBuilder msg = new StringBuilder("Problem deserializing property '") + .append(getName()) + .append("' (expected type: ") + .append(getType()) + .append("; actual type: ") + .append(actType).append(")"); + String origMsg = ClassUtil.exceptionMessage(e); + if (origMsg != null) { + msg.append(", problem: ") + .append(origMsg); + } else { + msg.append(" (no error message provided)"); + } + throw JsonMappingException.from(p, msg.toString(), e); + } + _throwAsIOE(p, e, config.isEnabled(MapperFeature.UNWRAP_ROOT_CAUSE)); + } + /** * Method that takes in exception of any type, and casts or wraps it * to an IOException or its subclass. @@ -637,6 +663,23 @@ protected void _throwAsIOE(JsonParser p, Exception e, Object value) throws IOExc _throwAsIOE(p, e); } + /** + * @since 2.7 + */ + protected IOException _throwAsIOE(JsonParser p, Exception e, boolean unwrapRootCause) throws IOException + { + ClassUtil.throwIfIOE(e); + ClassUtil.throwIfRTE(e); + + if (unwrapRootCause) { + // let's wrap the innermost problem + Throwable th = ClassUtil.getRootCause(e); + throw JsonMappingException.from(p, ClassUtil.exceptionMessage(th), th); + } else { + throw JsonMappingException.from(p, ClassUtil.exceptionMessage(e.getCause()), e.getCause()); + } + } + /** * @since 2.7 */ diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java index ec94d50939..270ce2962d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java @@ -140,7 +140,7 @@ public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, try { _setter.invoke(instance, value); } catch (Exception e) { - _throwAsIOE(p, e, value); + _throwAsIOE(ctxt.getConfig(), p, e, value); } } diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/RetainStacktrace4603Test.java b/src/test/java/com/fasterxml/jackson/databind/introspect/RetainStacktrace4603Test.java new file mode 100644 index 0000000000..1eba7a03e0 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/RetainStacktrace4603Test.java @@ -0,0 +1,73 @@ +package com.fasterxml.jackson.databind.introspect; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; + +// [databind#4603] Keep stacktrace when re-throwing exception with JsonMappingException +public class RetainStacktrace4603Test + extends DatabindTestUtil +{ + + static class CustomException extends RuntimeException { + public CustomException(String message, Throwable cause) { + super(message, cause); + } + } + + static class Feature1347DeserBean { + + int value; + + public void setValue(int x) { + try { + // Throws ArithmeticException + int a = x / 0; + } catch (Exception e) { + // should NOT get called, but just to + throw new CustomException("Should NOT get called", e); + } + } + } + + final String JSON = a2q("{'value':3}"); + + final ObjectMapper enabledMapper = jsonMapperBuilder() + .configure(MapperFeature.UNWRAP_ROOT_CAUSE, true) + .build(); + + final ObjectMapper disabledMapper = jsonMapperBuilder() + .configure(MapperFeature.UNWRAP_ROOT_CAUSE, false) + .build(); + + final ObjectMapper defaultMapper = newJsonMapper(); + + + // Whether disabled or enabled, should get ArithmeticException + @Test + public void testVisibility() + throws Exception + { + DatabindException enabledResult = _tryDeserializeWith(enabledMapper); + assertInstanceOf(ArithmeticException.class, enabledResult.getCause()); + + DatabindException defaultResult = _tryDeserializeWith(defaultMapper); + assertInstanceOf(ArithmeticException.class, defaultResult.getCause()); + + DatabindException disabledResult = _tryDeserializeWith(disabledMapper); + assertInstanceOf(CustomException.class, disabledResult.getCause()); + assertInstanceOf(ArithmeticException.class, disabledResult.getCause().getCause()); + + } + + private DatabindException _tryDeserializeWith(ObjectMapper mapper) { + return assertThrows(DatabindException.class, + () -> mapper.readValue(JSON, Feature1347DeserBean.class) + ); + } + +}