Skip to content

Commit e85f7f0

Browse files
committed
Add release notes wrt #3139, minor trimming
1 parent 4231bb1 commit e85f7f0

File tree

4 files changed

+54
-39
lines changed

4 files changed

+54
-39
lines changed

release-notes/CREDITS-2.x

+5
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,7 @@ Marc Carter (drekbour@github)
12001200
(2.12.0)
12011201
* Contributed #3055: Polymorphic subtype deduction ignores `defaultImpl` attribute
12021202
(2.12.2)
1203+
* Contributed #3139: Deserialization of "empty" subtype with DEDUCTION failed
12031204
12041205
Mike Gilbode (gilbode@github)
12051206
* Reported #792: Deserialization Not Working Right with Generic Types and Builders
@@ -1313,3 +1314,7 @@ Miguel G (Migwel@github)
13131314
Jelle Voost (jellevoost@github)
13141315
* Reported #3038: Two cases of incorrect error reporting about DeserializationFeature
13151316
(2.12.2)
1317+
1318+
JoeWoo (xJoeWoo@github)
1319+
* Reported #3139: Deserialization of "empty" subtype with DEDUCTION failed
1320+
(2.12.4)

release-notes/VERSION-2.x

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ Project: jackson-databind
44
=== Releases ===
55
------------------------------------------------------------------------
66

7+
2.12.4 (not yet released)
8+
9+
#3139: Deserialization of "empty" subtype with DEDUCTION failed
10+
(reported by JoeWoo; fix provided by drekbour@github)
11+
712
2.12.3 (12-Apr-2021)
813

914
#3108: `TypeFactory` cannot convert `Collection` sub-type without type parameters

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsDeductionTypeDeserializer.java

+12-9
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@
2525
* the absence of child fields infers a parent type. That is, every deducible subtype
2626
* MUST have some unique fields and the input data MUST contain said unique fields
2727
* to provide a <i>positive match</i>.
28+
*
29+
* @since 2.12
2830
*/
2931
public class AsDeductionTypeDeserializer extends AsPropertyTypeDeserializer
3032
{
3133
private static final long serialVersionUID = 1L;
34+
35+
// 03-May-2021, tatu: for [databind#3139], support for "empty" type
3236
private static final BitSet EMPTY_CLASS_FINGERPRINT = new BitSet(0);
3337

3438
// Fieldname -> bitmap-index of every field discovered, across all subtypes
@@ -106,16 +110,22 @@ public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ct
106110
return _deserializeTypedUsingDefaultImpl(p, ctxt, null, "Unexpected input");
107111
}
108112

113+
// 03-May-2021, tatu: [databind#3139] Special case, "empty" Object
114+
if (t == JsonToken.END_OBJECT) {
115+
String emptySubtype = subtypeFingerprints.get(EMPTY_CLASS_FINGERPRINT);
116+
if (emptySubtype != null) { // ... and an "empty" subtype registered
117+
return _deserializeTypedForId(p, ctxt, null, emptySubtype);
118+
}
119+
}
120+
109121
List<BitSet> candidates = new LinkedList<>(subtypeFingerprints.keySet());
110122

111123
// Record processed tokens as we must rewind once after deducing the deserializer to use
112124
@SuppressWarnings("resource")
113125
TokenBuffer tb = new TokenBuffer(p, ctxt);
114126
boolean ignoreCase = ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
115-
boolean incomingIsEmpty = true;
116127

117128
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
118-
incomingIsEmpty = false; // Has at least one property
119129
String name = p.currentName();
120130
if (ignoreCase) name = name.toLowerCase();
121131

@@ -131,13 +141,6 @@ public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ct
131141
}
132142
}
133143

134-
if (incomingIsEmpty) { // Special case - if we have empty content ...
135-
String emptySubtype = subtypeFingerprints.get(EMPTY_CLASS_FINGERPRINT);
136-
if (emptySubtype != null) { // ... and an "empty" subtype registered
137-
return _deserializeTypedForId(p, ctxt, null, emptySubtype);
138-
}
139-
}
140-
141144
// We have zero or multiple candidates, deduction has failed
142145
String msgToReportIfDefaultImplFailsToo = String.format("Cannot deduce unique subtype of %s (%d candidates match)", ClassUtil.getTypeDescription(_baseType), candidates.size());
143146
return _deserializeTypedUsingDefaultImpl(p, ctxt, tb, msgToReportIfDefaultImplFailsToo);

src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicDeduction.java

+32-30
Original file line numberDiff line numberDiff line change
@@ -60,33 +60,35 @@ static class Box {
6060
/**********************************************************
6161
*/
6262

63-
private static final String deadCatJson = aposToQuotes("{'name':'Felix','causeOfDeath':'entropy'}");
64-
private static final String liveCatJson = aposToQuotes("{'name':'Felix','angry':true}");
65-
private static final String luckyCatJson = aposToQuotes("{'name':'Felix','angry':true,'lives':8}");
66-
private static final String ambiguousCatJson = aposToQuotes("{'name':'Felix','age':2}");
67-
private static final String fleabagJson = aposToQuotes("{}");
68-
private static final String box1Json = aposToQuotes("{'feline':" + liveCatJson + "}");
69-
private static final String box2Json = aposToQuotes("{'feline':" + deadCatJson + "}");
70-
private static final String box3Json = aposToQuotes("{'feline':" + fleabagJson + "}");
71-
private static final String box4Json = aposToQuotes("{'feline':null}");
72-
private static final String box5Json = aposToQuotes("{}");
73-
private static final String arrayOfCatsJson = aposToQuotes("[" + liveCatJson + "," + deadCatJson + "]");
74-
private static final String mapOfCatsJson = aposToQuotes("{'live':" + liveCatJson + "}");
63+
private static final String deadCatJson = a2q("{'name':'Felix','causeOfDeath':'entropy'}");
64+
private static final String liveCatJson = a2q("{'name':'Felix','angry':true}");
65+
private static final String luckyCatJson = a2q("{'name':'Felix','angry':true,'lives':8}");
66+
private static final String ambiguousCatJson = a2q("{'name':'Felix','age':2}");
67+
private static final String fleabagJson = a2q("{}");
68+
private static final String box1Json = a2q("{'feline':" + liveCatJson + "}");
69+
private static final String box2Json = a2q("{'feline':" + deadCatJson + "}");
70+
private static final String box3Json = a2q("{'feline':" + fleabagJson + "}");
71+
private static final String box4Json = a2q("{'feline':null}");
72+
private static final String box5Json = a2q("{}");
73+
private static final String arrayOfCatsJson = a2q("[" + liveCatJson + "," + deadCatJson + "]");
74+
private static final String mapOfCatsJson = a2q("{'live':" + liveCatJson + "}");
7575

7676
/*
7777
/**********************************************************
7878
/* Test methods
7979
/**********************************************************
8080
*/
8181

82+
private final ObjectMapper MAPPER = newJsonMapper();
83+
8284
public void testSimpleInference() throws Exception {
83-
Cat cat = sharedMapper().readValue(liveCatJson, Cat.class);
85+
Cat cat = MAPPER.readValue(liveCatJson, Cat.class);
8486
assertTrue(cat instanceof LiveCat);
8587
assertSame(cat.getClass(), LiveCat.class);
8688
assertEquals("Felix", cat.name);
8789
assertTrue(((LiveCat)cat).angry);
8890

89-
cat = sharedMapper().readValue(deadCatJson, Cat.class);
91+
cat = MAPPER.readValue(deadCatJson, Cat.class);
9092
assertTrue(cat instanceof DeadCat);
9193
assertSame(cat.getClass(), DeadCat.class);
9294
assertEquals("Felix", cat.name);
@@ -95,7 +97,7 @@ public void testSimpleInference() throws Exception {
9597

9698
public void testSimpleInferenceOfEmptySubtype() throws Exception {
9799
// Given:
98-
ObjectMapper mapper = sharedMapper();
100+
ObjectMapper mapper = MAPPER;
99101
// When:
100102
Feline feline = mapper.readValue(fleabagJson, Feline.class);
101103
// Then:
@@ -104,7 +106,7 @@ public void testSimpleInferenceOfEmptySubtype() throws Exception {
104106

105107
public void testSimpleInferenceOfEmptySubtypeDoesntMatchNull() throws Exception {
106108
// Given:
107-
ObjectMapper mapper = sharedMapper();
109+
ObjectMapper mapper = MAPPER;
108110
// When:
109111
Feline feline = mapper.readValue("null", Feline.class);
110112
// Then:
@@ -136,52 +138,52 @@ public void testCaseInsensitiveInference() throws Exception {
136138
// }
137139

138140
public void testContainedInference() throws Exception {
139-
Box box = sharedMapper().readValue(box1Json, Box.class);
141+
Box box = MAPPER.readValue(box1Json, Box.class);
140142
assertTrue(box.feline instanceof LiveCat);
141143
assertSame(box.feline.getClass(), LiveCat.class);
142144
assertEquals("Felix", ((LiveCat)box.feline).name);
143145
assertTrue(((LiveCat)box.feline).angry);
144146

145-
box = sharedMapper().readValue(box2Json, Box.class);
147+
box = MAPPER.readValue(box2Json, Box.class);
146148
assertTrue(box.feline instanceof DeadCat);
147149
assertSame(box.feline.getClass(), DeadCat.class);
148150
assertEquals("Felix", ((DeadCat)box.feline).name);
149151
assertEquals("entropy", ((DeadCat)box.feline).causeOfDeath);
150152
}
151153

152154
public void testContainedInferenceOfEmptySubtype() throws Exception {
153-
Box box = sharedMapper().readValue(box3Json, Box.class);
155+
Box box = MAPPER.readValue(box3Json, Box.class);
154156
assertTrue(box.feline instanceof Fleabag);
155157

156-
box = sharedMapper().readValue(box4Json, Box.class);
158+
box = MAPPER.readValue(box4Json, Box.class);
157159
assertNull("null != {}", box.feline);
158160

159-
box = sharedMapper().readValue(box5Json, Box.class);
161+
box = MAPPER.readValue(box5Json, Box.class);
160162
assertNull("<absent> != {}", box.feline);
161163
}
162164

163165
public void testListInference() throws Exception {
164166
JavaType listOfCats = TypeFactory.defaultInstance().constructParametricType(List.class, Cat.class);
165-
List<Cat> boxes = sharedMapper().readValue(arrayOfCatsJson, listOfCats);
167+
List<Cat> boxes = MAPPER.readValue(arrayOfCatsJson, listOfCats);
166168
assertTrue(boxes.get(0) instanceof LiveCat);
167169
assertTrue(boxes.get(1) instanceof DeadCat);
168170
}
169171

170172
public void testMapInference() throws Exception {
171173
JavaType mapOfCats = TypeFactory.defaultInstance().constructParametricType(Map.class, String.class, Cat.class);
172-
Map<String, Cat> map = sharedMapper().readValue(mapOfCatsJson, mapOfCats);
174+
Map<String, Cat> map = MAPPER.readValue(mapOfCatsJson, mapOfCats);
173175
assertEquals(1, map.size());
174176
assertTrue(map.entrySet().iterator().next().getValue() instanceof LiveCat);
175177
}
176178

177179
public void testArrayInference() throws Exception {
178-
Cat[] boxes = sharedMapper().readValue(arrayOfCatsJson, Cat[].class);
180+
Cat[] boxes = MAPPER.readValue(arrayOfCatsJson, Cat[].class);
179181
assertTrue(boxes[0] instanceof LiveCat);
180182
assertTrue(boxes[1] instanceof DeadCat);
181183
}
182184

183185
public void testIgnoreProperties() throws Exception {
184-
Cat cat = sharedMapper().reader()
186+
Cat cat = MAPPER.reader()
185187
.without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
186188
.readValue(luckyCatJson, Cat.class);
187189
assertTrue(cat instanceof LiveCat);
@@ -210,7 +212,7 @@ public void testAmbiguousClasses() throws Exception {
210212

211213
public void testAmbiguousProperties() throws Exception {
212214
try {
213-
/*Cat cat =*/ sharedMapper().readValue(ambiguousCatJson, Cat.class);
215+
/*Cat cat =*/ MAPPER.readValue(ambiguousCatJson, Cat.class);
214216
fail("Should not get here");
215217
} catch (InvalidTypeIdException e) {
216218
verifyException(e, "Cannot deduce unique subtype");
@@ -250,20 +252,20 @@ public void testDefaultImpl() throws Exception {
250252
public void testSimpleSerialization() throws Exception {
251253
// Given:
252254
JavaType listOfCats = TypeFactory.defaultInstance().constructParametricType(List.class, Cat.class);
253-
List<Cat> list = sharedMapper().readValue(arrayOfCatsJson, listOfCats);
255+
List<Cat> list = MAPPER.readValue(arrayOfCatsJson, listOfCats);
254256
Cat cat = list.get(0);
255257
// When:
256-
String json = sharedMapper().writeValueAsString(cat);
258+
String json = MAPPER.writeValueAsString(cat);
257259
// Then:
258260
assertEquals(liveCatJson, json);
259261
}
260262

261263
public void testListSerialization() throws Exception {
262264
// Given:
263265
JavaType listOfCats = TypeFactory.defaultInstance().constructParametricType(List.class, Cat.class);
264-
List<Cat> list = sharedMapper().readValue(arrayOfCatsJson, listOfCats);
266+
List<Cat> list = MAPPER.readValue(arrayOfCatsJson, listOfCats);
265267
// When:
266-
String json = sharedMapper().writeValueAsString(list);
268+
String json = MAPPER.writeValueAsString(list);
267269
// Then:
268270
assertEquals(arrayOfCatsJson, json);
269271
}

0 commit comments

Comments
 (0)