Skip to content

Commit 1eee6fe

Browse files
authored
Improve handling of wrapping as DatabindException to try to preserve location info (#5091)
1 parent dd34fb6 commit 1eee6fe

File tree

3 files changed

+87
-36
lines changed

3 files changed

+87
-36
lines changed

src/main/java/tools/jackson/databind/DatabindException.java

+53-19
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ protected DatabindException(Closeable processor, String msg, Throwable problem)
3535
super(processor, msg, problem);
3636
}
3737

38-
protected DatabindException(Closeable processor, String msg, TokenStreamLocation loc)
39-
{
40-
super(msg, loc, null);
38+
protected DatabindException(Closeable processor, String msg, TokenStreamLocation loc) {
39+
super(processor, msg, loc, null);
4140
}
4241

42+
protected DatabindException(Closeable processor, String msg, TokenStreamLocation loc,
43+
Throwable rootCause) {
44+
super(processor, msg, loc, rootCause);
45+
}
4346
protected DatabindException(String msg, TokenStreamLocation loc, Throwable rootCause) {
4447
super(msg, loc, rootCause);
4548
}
@@ -74,21 +77,62 @@ public static DatabindException from(DeserializationContext ctxt, String msg) {
7477
return new DatabindException(_parser(ctxt), msg);
7578
}
7679

80+
public static DatabindException from(DeserializationContext ctxt, String msg, Throwable problem) {
81+
return new DatabindException(_parser(ctxt), msg, problem);
82+
}
83+
84+
public static DatabindException from(SerializationContext ctxt, String msg) {
85+
return new DatabindException(_generator(ctxt), msg);
86+
}
87+
88+
public static DatabindException from(SerializationContext ctxt, String msg, Throwable problem) {
89+
return new DatabindException(_generator(ctxt), msg, problem);
90+
}
91+
7792
// // "Overrides": methods with name same as ones from JacksonException
7893
// // (but for static methods no real overriding, static dispatch)
7994

8095
public static JacksonException wrapWithPath(DeserializationContext ctxt,
8196
Throwable src, Reference ref) {
82-
// !!! TODO: get `JsonParser`, location from `ctxt`
83-
return JacksonException.wrapWithPath(src, ref,
84-
(msg, cause) -> new DatabindException(msg, null, cause));
97+
JsonParser p = _parser(ctxt);
98+
99+
// Copied from JacksonException.wrapWithPath()
100+
JacksonException jme;
101+
if (src instanceof JacksonException) {
102+
jme = (JacksonException) src;
103+
} else {
104+
// [databind#2128]: try to avoid duplication
105+
String msg = _exceptionMessage(src);
106+
// Let's use a more meaningful placeholder if all we have is null
107+
if (msg == null || msg.isEmpty()) {
108+
msg = "(was "+src.getClass().getName()+")";
109+
}
110+
TokenStreamLocation loc = (p == null) ? null : p.currentLocation();
111+
jme = new DatabindException(p, msg, loc, src);
112+
}
113+
jme.prependPath(ref);
114+
return jme;
85115
}
86116

87117
public static JacksonException wrapWithPath(SerializationContext ctxt,
88118
Throwable src, Reference ref) {
89-
// !!! TODO: get `JsonGenerator` from `ctxt`
90-
return JacksonException.wrapWithPath(src, ref,
91-
(msg, cause) -> new DatabindException(msg, null, cause));
119+
JsonGenerator g = _generator(ctxt);
120+
121+
// Copied from JacksonException.wrapWithPath()
122+
JacksonException jme;
123+
if (src instanceof JacksonException) {
124+
jme = (JacksonException) src;
125+
} else {
126+
String msg = _exceptionMessage(src);
127+
if (msg == null || msg.isEmpty()) {
128+
msg = "(was "+src.getClass().getName()+")";
129+
}
130+
// 2025-04-11, tatu: No location from generator, currently
131+
TokenStreamLocation loc = null;
132+
jme = new DatabindException(g, msg, loc, src);
133+
}
134+
jme.prependPath(ref);
135+
return jme;
92136
}
93137

94138
/*
@@ -101,16 +145,6 @@ private static JsonParser _parser(DeserializationContext ctxt) {
101145
return (ctxt == null) ? null : ctxt.getParser();
102146
}
103147

104-
public static DatabindException from(SerializationContext ctxt, String msg) {
105-
return new DatabindException(_generator(ctxt), msg);
106-
}
107-
108-
public static DatabindException from(SerializationContext ctxt, String msg, Throwable problem) {
109-
// 17-Aug-2015, tatu: As per [databind#903] this is bit problematic as
110-
// SerializationContext instance does not currently hold on to generator...
111-
return new DatabindException(_generator(ctxt), msg, problem);
112-
}
113-
114148
private static JsonGenerator _generator(SerializationContext ctxt) {
115149
return (ctxt == null) ? null : ctxt.getGenerator();
116150
}

src/main/java/tools/jackson/databind/deser/SettableAnyProperty.java

+18-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package tools.jackson.databind.deser;
22

3+
import java.lang.reflect.InvocationTargetException;
34
import java.util.HashMap;
45
import java.util.LinkedHashMap;
56
import java.util.Map;
@@ -247,7 +248,7 @@ public void set(DeserializationContext ctxt, Object instance, Object propName, O
247248
} catch (JacksonException e) {
248249
throw e;
249250
} catch (Exception e) {
250-
_throwAsIOE(e, propName, value);
251+
_throwAsIOE(ctxt, e, propName, value);
251252
}
252253
}
253254

@@ -261,31 +262,38 @@ protected abstract void _set(DeserializationContext ctxt, Object instance, Objec
261262
*/
262263

263264
/**
264-
* @param e Exception to re-throw or wrap
265+
* @param t Exception to re-throw or wrap
265266
* @param propName Name of property (from Json input) to set
266267
* @param value Value of the property
267268
*/
268-
protected void _throwAsIOE(Exception e, Object propName, Object value)
269+
protected void _throwAsIOE(DeserializationContext ctxt, Throwable t, Object propName, Object value)
269270
throws JacksonException
270271
{
271-
if (e instanceof IllegalArgumentException) {
272+
if (t instanceof IllegalArgumentException) {
272273
String actType = ClassUtil.classNameOf(value);
273274
StringBuilder msg = new StringBuilder("Problem deserializing \"any-property\" '").append(propName);
274275
msg.append("' of class "+getClassName()+" (expected type: ").append(_type);
275276
msg.append("; actual type: ").append(actType).append(")");
276-
String origMsg = ClassUtil.exceptionMessage(e);
277+
String origMsg = ClassUtil.exceptionMessage(t);
277278
if (origMsg != null) {
278279
msg.append(", problem: ").append(origMsg);
279280
} else {
280281
msg.append(" (no error message provided)");
281282
}
282-
throw DatabindException.from((JsonParser) null, msg.toString(), e);
283+
throw DatabindException.from(ctxt, msg.toString(), t);
283284
}
284-
ClassUtil.throwIfJacksonE(e);
285-
ClassUtil.throwIfRTE(e);
285+
ClassUtil.throwIfJacksonE(t);
286+
ClassUtil.throwIfRTE(t);
286287
// let's wrap the innermost problem
287-
Throwable t = ClassUtil.getRootCause(e);
288-
throw DatabindException.from((JsonParser) null, ClassUtil.exceptionMessage(t), t);
288+
// 11-Apr-2025: [databind#4603] no more general unwrapping
289+
// Throwable t = ClassUtil.getRootCause(e);
290+
// ... just one:
291+
if (t instanceof InvocationTargetException ite) {
292+
t = ite.getTargetException();
293+
ClassUtil.throwIfRTE(t);
294+
ClassUtil.throwIfJacksonE(t);
295+
}
296+
throw DatabindException.from(ctxt, ClassUtil.exceptionMessage(t), t);
289297
}
290298

291299
private String getClassName() { return ClassUtil.nameOf(_setter.getDeclaringClass()); }

src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java

+16-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.junit.jupiter.api.Test;
77

88
import tools.jackson.core.JacksonException;
9+
import tools.jackson.core.TokenStreamLocation;
910
import tools.jackson.databind.*;
1011
import tools.jackson.databind.testutil.DatabindTestUtil;
1112

@@ -56,25 +57,28 @@ public String getQux() {
5657
}
5758
}
5859

59-
private static final String JSON = a2q("{'value':3}");
60-
6160
private final ObjectMapper MAPPER = newJsonMapper();
6261

6362
// Whether disabled or enabled, should get ArithmeticException
6463
@Test
6564
public void testExceptionWrappingConfiguration()
6665
throws Exception
6766
{
68-
JacksonException disabledResult = _tryDeserializeWith(MAPPER);
69-
assertInstanceOf(DatabindException.class, disabledResult);
67+
JacksonException result = _tryDeserializeWith(MAPPER);
68+
assertInstanceOf(DatabindException.class, result);
7069
// We are throwing exception inside a setter, but InvocationTargetException
7170
// will still be unwrapped
72-
assertInstanceOf(CustomException.class, disabledResult.getCause());
71+
assertInstanceOf(CustomException.class, result.getCause());
72+
TokenStreamLocation loc = result.getLocation();
73+
assertNotSame(TokenStreamLocation.NA, loc);
74+
// happens to point to location after `3`
75+
assertEquals(1, loc.getLineNr());
76+
assertEquals(11, loc.getColumnNr());
7377
}
7478

7579
private JacksonException _tryDeserializeWith(ObjectMapper mapper) {
7680
return assertThrows(JacksonException.class,
77-
() -> mapper.readValue(JSON, Feature1347DeserBean.class)
81+
() -> mapper.readValue(a2q("{'value':3}"), Feature1347DeserBean.class)
7882
);
7983
}
8084

@@ -83,7 +87,12 @@ public void testWithAnySetter()
8387
throws Exception
8488
{
8589
DatabindException result = assertThrows(DatabindException.class,
86-
() -> MAPPER.readValue(a2q("{'a':3}"), AnySetterBean.class));
90+
() -> MAPPER.readValue(a2q("{'a':72}"), AnySetterBean.class));
8791
assertInstanceOf(CustomException.class, result.getCause());
92+
TokenStreamLocation loc = result.getLocation();
93+
assertNotSame(TokenStreamLocation.NA, loc);
94+
// happens to point to location after `72`
95+
assertEquals(1, loc.getLineNr());
96+
assertEquals(8, loc.getColumnNr());
8897
}
8998
}

0 commit comments

Comments
 (0)