Skip to content

Commit a05e2cb

Browse files
Support suppressed property when deserializing Throwable (FasterXML#3179)
Implement #31767: support `suppressed` property when deserializing Throwable
1 parent 91a98ac commit a05e2cb

File tree

2 files changed

+102
-27
lines changed

2 files changed

+102
-27
lines changed

src/main/java/com/fasterxml/jackson/databind/deser/std/ThrowableDeserializer.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class ThrowableDeserializer
1919
private static final long serialVersionUID = 1L;
2020

2121
protected final static String PROP_NAME_MESSAGE = "message";
22+
protected final static String PROP_NAME_SUPPRESSED = "suppressed";
2223

2324
/*
2425
/************************************************************
@@ -82,6 +83,7 @@ public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) t
8283

8384
Object throwable = null;
8485
Object[] pending = null;
86+
Throwable[] suppressed = null;
8587
int pendingIx = 0;
8688

8789
for (; !p.hasToken(JsonToken.END_OBJECT); p.nextToken()) {
@@ -109,17 +111,17 @@ public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) t
109111
if (isMessage) {
110112
if (hasStringCreator) {
111113
throwable = _valueInstantiator.createFromString(ctxt, p.getValueAsString());
112-
// any pending values?
113-
if (pending != null) {
114-
for (int i = 0, len = pendingIx; i < len; i += 2) {
115-
prop = (SettableBeanProperty)pending[i];
116-
prop.set(throwable, pending[i+1]);
117-
}
118-
pending = null;
119-
}
120114
continue;
121115
}
122116
}
117+
118+
// Maybe it's "suppressed"?
119+
final boolean isSuppressed = PROP_NAME_SUPPRESSED.equals(propName);
120+
if (isSuppressed) {
121+
suppressed = ctxt.readValue(p, Throwable[].class);
122+
continue;
123+
}
124+
123125
// Things marked as ignorable should not be passed to any setter
124126
if ((_ignorableProps != null) && _ignorableProps.contains(propName)) {
125127
p.skipChildren();
@@ -148,14 +150,24 @@ public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) t
148150
} else {
149151
throwable = _valueInstantiator.createUsingDefault(ctxt);
150152
}
151-
// any pending values?
152-
if (pending != null) {
153-
for (int i = 0, len = pendingIx; i < len; i += 2) {
154-
SettableBeanProperty prop = (SettableBeanProperty)pending[i];
155-
prop.set(throwable, pending[i+1]);
156-
}
153+
}
154+
155+
// any pending values?
156+
if (pending != null) {
157+
for (int i = 0, len = pendingIx; i < len; i += 2) {
158+
SettableBeanProperty prop = (SettableBeanProperty)pending[i];
159+
prop.set(throwable, pending[i+1]);
157160
}
158161
}
162+
163+
// any suppressed exceptions?
164+
if (suppressed != null && throwable instanceof Throwable) {
165+
Throwable t = (Throwable) throwable;
166+
for (Throwable s : suppressed) {
167+
t.addSuppressed(s);
168+
}
169+
}
170+
159171
return throwable;
160172
}
161173
}

src/test/java/com/fasterxml/jackson/databind/exc/ExceptionDeserializationTest.java

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import com.fasterxml.jackson.annotation.*;
77

88
import com.fasterxml.jackson.databind.*;
9+
import com.fasterxml.jackson.databind.json.JsonMapper;
10+
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
11+
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
912

1013
/**
1114
* Unit tests for verifying that simple exceptions can be deserialized.
@@ -20,7 +23,7 @@ static class MyException extends Exception
2023

2124
protected String myMessage;
2225
protected HashMap<String,Object> stuff = new HashMap<String, Object>();
23-
26+
2427
@JsonCreator
2528
MyException(@JsonProperty("message") String msg, @JsonProperty("value") int v)
2629
{
@@ -30,7 +33,7 @@ static class MyException extends Exception
3033
}
3134

3235
public int getValue() { return value; }
33-
36+
3437
public String getFoo() { return "bar"; }
3538

3639
@JsonAnySetter public void setter(String key, Object value)
@@ -52,7 +55,7 @@ static class MyNoArgException extends Exception
5255
*/
5356

5457
private final ObjectMapper MAPPER = new ObjectMapper();
55-
58+
5659
public void testIOException() throws IOException
5760
{
5861
IOException ioe = new IOException("TEST");
@@ -96,9 +99,10 @@ public void testJDK7SuppressionProperty() throws IOException
9699
Exception exc = MAPPER.readValue("{\"suppressed\":[]}", IOException.class);
97100
assertNotNull(exc);
98101
}
99-
102+
100103
// [databind#381]
101-
public void testSingleValueArrayDeserialization() throws Exception {
104+
public void testSingleValueArrayDeserialization() throws Exception
105+
{
102106
final ObjectMapper mapper = new ObjectMapper();
103107
mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
104108
final IOException exp;
@@ -108,13 +112,72 @@ public void testSingleValueArrayDeserialization() throws Exception {
108112
exp = internal;
109113
}
110114
final String value = "[" + mapper.writeValueAsString(exp) + "]";
111-
115+
112116
final IOException cloned = mapper.readValue(value, IOException.class);
113-
assertEquals(exp.getMessage(), cloned.getMessage());
114-
115-
assertEquals(exp.getStackTrace().length, cloned.getStackTrace().length);
116-
for (int i = 0; i < exp.getStackTrace().length; i ++) {
117-
_assertEquality(i, exp.getStackTrace()[i], cloned.getStackTrace()[i]);
117+
assertEquals(exp.getMessage(), cloned.getMessage());
118+
119+
_assertEquality(exp.getStackTrace(), cloned.getStackTrace());
120+
}
121+
122+
public void testExceptionCauseDeserialization() throws IOException
123+
{
124+
ObjectMapper mapper = new ObjectMapper();
125+
126+
final IOException exp = new IOException("the outer exception", new Throwable("the cause"));
127+
128+
final String value = mapper.writeValueAsString(exp);
129+
final IOException act = mapper.readValue(value, IOException.class);
130+
131+
assertNotNull(act.getCause());
132+
assertEquals(exp.getCause().getMessage(), act.getCause().getMessage());
133+
_assertEquality(exp.getCause().getStackTrace(), act.getCause().getStackTrace());
134+
}
135+
136+
137+
public void testSuppressedGenericThrowableDeserialization() throws IOException
138+
{
139+
ObjectMapper mapper = new ObjectMapper();
140+
141+
final IOException exp = new IOException("the outer exception");
142+
exp.addSuppressed(new Throwable("the suppressed exception"));
143+
144+
final String value = mapper.writeValueAsString(exp);
145+
final IOException act = mapper.readValue(value, IOException.class);
146+
147+
assertNotNull(act.getSuppressed());
148+
assertEquals(1, act.getSuppressed().length);
149+
assertEquals(exp.getSuppressed()[0].getMessage(), act.getSuppressed()[0].getMessage());
150+
_assertEquality(exp.getSuppressed()[0].getStackTrace(), act.getSuppressed()[0].getStackTrace());
151+
}
152+
153+
public void testSuppressedTypedExceptionDeserialization() throws IOException
154+
{
155+
PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator.builder()
156+
.allowIfSubTypeIsArray()
157+
.allowIfSubType(Throwable.class)
158+
.build();
159+
160+
ObjectMapper mapper = JsonMapper.builder()
161+
.activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL)
162+
.build();
163+
164+
final IOException exp = new IOException("the outer exception");
165+
exp.addSuppressed(new IllegalArgumentException("the suppressed exception"));
166+
167+
final String value = mapper.writeValueAsString(exp);
168+
final IOException act = mapper.readValue(value, IOException.class);
169+
170+
assertNotNull(act.getSuppressed());
171+
assertEquals(1, act.getSuppressed().length);
172+
assertEquals(IllegalArgumentException.class, act.getSuppressed()[0].getClass());
173+
assertEquals(exp.getSuppressed()[0].getMessage(), act.getSuppressed()[0].getMessage());
174+
_assertEquality(exp.getSuppressed()[0].getStackTrace(), act.getSuppressed()[0].getStackTrace());
175+
}
176+
177+
private void _assertEquality(StackTraceElement[] exp, StackTraceElement[] act) {
178+
assertEquals(exp.length, act.length);
179+
for (int i = 0; i < exp.length; i++) {
180+
_assertEquality(i, exp[i], act[i]);
118181
}
119182
}
120183

@@ -145,15 +208,15 @@ protected void _assertEquality(int ix, String prop,
145208
public void testSingleValueArrayDeserializationException() throws Exception {
146209
final ObjectMapper mapper = new ObjectMapper();
147210
mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
148-
211+
149212
final IOException exp;
150213
try {
151214
throw new IOException("testing");
152215
} catch (IOException internal) {
153216
exp = internal;
154217
}
155218
final String value = "[" + mapper.writeValueAsString(exp) + "]";
156-
219+
157220
try {
158221
mapper.readValue(value, IOException.class);
159222
fail("Exception not thrown when attempting to deserialize an IOException wrapped in a single value array with UNWRAP_SINGLE_VALUE_ARRAYS disabled");

0 commit comments

Comments
 (0)