Skip to content

Commit

Permalink
Add release notes wrt #3139, minor trimming
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed May 3, 2021
1 parent 4231bb1 commit e85f7f0
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 39 deletions.
5 changes: 5 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,7 @@ Marc Carter (drekbour@github)
(2.12.0)
* Contributed #3055: Polymorphic subtype deduction ignores `defaultImpl` attribute
(2.12.2)
* Contributed #3139: Deserialization of "empty" subtype with DEDUCTION failed
Mike Gilbode (gilbode@github)
* Reported #792: Deserialization Not Working Right with Generic Types and Builders
Expand Down Expand Up @@ -1313,3 +1314,7 @@ Miguel G (Migwel@github)
Jelle Voost (jellevoost@github)
* Reported #3038: Two cases of incorrect error reporting about DeserializationFeature
(2.12.2)
JoeWoo (xJoeWoo@github)
* Reported #3139: Deserialization of "empty" subtype with DEDUCTION failed
(2.12.4)
5 changes: 5 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Project: jackson-databind
=== Releases ===
------------------------------------------------------------------------

2.12.4 (not yet released)

#3139: Deserialization of "empty" subtype with DEDUCTION failed
(reported by JoeWoo; fix provided by drekbour@github)

2.12.3 (12-Apr-2021)

#3108: `TypeFactory` cannot convert `Collection` sub-type without type parameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@
* the absence of child fields infers a parent type. That is, every deducible subtype
* MUST have some unique fields and the input data MUST contain said unique fields
* to provide a <i>positive match</i>.
*
* @since 2.12
*/
public class AsDeductionTypeDeserializer extends AsPropertyTypeDeserializer
{
private static final long serialVersionUID = 1L;

// 03-May-2021, tatu: for [databind#3139], support for "empty" type
private static final BitSet EMPTY_CLASS_FINGERPRINT = new BitSet(0);

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

// 03-May-2021, tatu: [databind#3139] Special case, "empty" Object
if (t == JsonToken.END_OBJECT) {
String emptySubtype = subtypeFingerprints.get(EMPTY_CLASS_FINGERPRINT);
if (emptySubtype != null) { // ... and an "empty" subtype registered
return _deserializeTypedForId(p, ctxt, null, emptySubtype);
}
}

List<BitSet> candidates = new LinkedList<>(subtypeFingerprints.keySet());

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

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

Expand All @@ -131,13 +141,6 @@ public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ct
}
}

if (incomingIsEmpty) { // Special case - if we have empty content ...
String emptySubtype = subtypeFingerprints.get(EMPTY_CLASS_FINGERPRINT);
if (emptySubtype != null) { // ... and an "empty" subtype registered
return _deserializeTypedForId(p, ctxt, null, emptySubtype);
}
}

// We have zero or multiple candidates, deduction has failed
String msgToReportIfDefaultImplFailsToo = String.format("Cannot deduce unique subtype of %s (%d candidates match)", ClassUtil.getTypeDescription(_baseType), candidates.size());
return _deserializeTypedUsingDefaultImpl(p, ctxt, tb, msgToReportIfDefaultImplFailsToo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,33 +60,35 @@ static class Box {
/**********************************************************
*/

private static final String deadCatJson = aposToQuotes("{'name':'Felix','causeOfDeath':'entropy'}");
private static final String liveCatJson = aposToQuotes("{'name':'Felix','angry':true}");
private static final String luckyCatJson = aposToQuotes("{'name':'Felix','angry':true,'lives':8}");
private static final String ambiguousCatJson = aposToQuotes("{'name':'Felix','age':2}");
private static final String fleabagJson = aposToQuotes("{}");
private static final String box1Json = aposToQuotes("{'feline':" + liveCatJson + "}");
private static final String box2Json = aposToQuotes("{'feline':" + deadCatJson + "}");
private static final String box3Json = aposToQuotes("{'feline':" + fleabagJson + "}");
private static final String box4Json = aposToQuotes("{'feline':null}");
private static final String box5Json = aposToQuotes("{}");
private static final String arrayOfCatsJson = aposToQuotes("[" + liveCatJson + "," + deadCatJson + "]");
private static final String mapOfCatsJson = aposToQuotes("{'live':" + liveCatJson + "}");
private static final String deadCatJson = a2q("{'name':'Felix','causeOfDeath':'entropy'}");
private static final String liveCatJson = a2q("{'name':'Felix','angry':true}");
private static final String luckyCatJson = a2q("{'name':'Felix','angry':true,'lives':8}");
private static final String ambiguousCatJson = a2q("{'name':'Felix','age':2}");
private static final String fleabagJson = a2q("{}");
private static final String box1Json = a2q("{'feline':" + liveCatJson + "}");
private static final String box2Json = a2q("{'feline':" + deadCatJson + "}");
private static final String box3Json = a2q("{'feline':" + fleabagJson + "}");
private static final String box4Json = a2q("{'feline':null}");
private static final String box5Json = a2q("{}");
private static final String arrayOfCatsJson = a2q("[" + liveCatJson + "," + deadCatJson + "]");
private static final String mapOfCatsJson = a2q("{'live':" + liveCatJson + "}");

/*
/**********************************************************
/* Test methods
/**********************************************************
*/

private final ObjectMapper MAPPER = newJsonMapper();

public void testSimpleInference() throws Exception {
Cat cat = sharedMapper().readValue(liveCatJson, Cat.class);
Cat cat = MAPPER.readValue(liveCatJson, Cat.class);
assertTrue(cat instanceof LiveCat);
assertSame(cat.getClass(), LiveCat.class);
assertEquals("Felix", cat.name);
assertTrue(((LiveCat)cat).angry);

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

public void testSimpleInferenceOfEmptySubtype() throws Exception {
// Given:
ObjectMapper mapper = sharedMapper();
ObjectMapper mapper = MAPPER;
// When:
Feline feline = mapper.readValue(fleabagJson, Feline.class);
// Then:
Expand All @@ -104,7 +106,7 @@ public void testSimpleInferenceOfEmptySubtype() throws Exception {

public void testSimpleInferenceOfEmptySubtypeDoesntMatchNull() throws Exception {
// Given:
ObjectMapper mapper = sharedMapper();
ObjectMapper mapper = MAPPER;
// When:
Feline feline = mapper.readValue("null", Feline.class);
// Then:
Expand Down Expand Up @@ -136,52 +138,52 @@ public void testCaseInsensitiveInference() throws Exception {
// }

public void testContainedInference() throws Exception {
Box box = sharedMapper().readValue(box1Json, Box.class);
Box box = MAPPER.readValue(box1Json, Box.class);
assertTrue(box.feline instanceof LiveCat);
assertSame(box.feline.getClass(), LiveCat.class);
assertEquals("Felix", ((LiveCat)box.feline).name);
assertTrue(((LiveCat)box.feline).angry);

box = sharedMapper().readValue(box2Json, Box.class);
box = MAPPER.readValue(box2Json, Box.class);
assertTrue(box.feline instanceof DeadCat);
assertSame(box.feline.getClass(), DeadCat.class);
assertEquals("Felix", ((DeadCat)box.feline).name);
assertEquals("entropy", ((DeadCat)box.feline).causeOfDeath);
}

public void testContainedInferenceOfEmptySubtype() throws Exception {
Box box = sharedMapper().readValue(box3Json, Box.class);
Box box = MAPPER.readValue(box3Json, Box.class);
assertTrue(box.feline instanceof Fleabag);

box = sharedMapper().readValue(box4Json, Box.class);
box = MAPPER.readValue(box4Json, Box.class);
assertNull("null != {}", box.feline);

box = sharedMapper().readValue(box5Json, Box.class);
box = MAPPER.readValue(box5Json, Box.class);
assertNull("<absent> != {}", box.feline);
}

public void testListInference() throws Exception {
JavaType listOfCats = TypeFactory.defaultInstance().constructParametricType(List.class, Cat.class);
List<Cat> boxes = sharedMapper().readValue(arrayOfCatsJson, listOfCats);
List<Cat> boxes = MAPPER.readValue(arrayOfCatsJson, listOfCats);
assertTrue(boxes.get(0) instanceof LiveCat);
assertTrue(boxes.get(1) instanceof DeadCat);
}

public void testMapInference() throws Exception {
JavaType mapOfCats = TypeFactory.defaultInstance().constructParametricType(Map.class, String.class, Cat.class);
Map<String, Cat> map = sharedMapper().readValue(mapOfCatsJson, mapOfCats);
Map<String, Cat> map = MAPPER.readValue(mapOfCatsJson, mapOfCats);
assertEquals(1, map.size());
assertTrue(map.entrySet().iterator().next().getValue() instanceof LiveCat);
}

public void testArrayInference() throws Exception {
Cat[] boxes = sharedMapper().readValue(arrayOfCatsJson, Cat[].class);
Cat[] boxes = MAPPER.readValue(arrayOfCatsJson, Cat[].class);
assertTrue(boxes[0] instanceof LiveCat);
assertTrue(boxes[1] instanceof DeadCat);
}

public void testIgnoreProperties() throws Exception {
Cat cat = sharedMapper().reader()
Cat cat = MAPPER.reader()
.without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.readValue(luckyCatJson, Cat.class);
assertTrue(cat instanceof LiveCat);
Expand Down Expand Up @@ -210,7 +212,7 @@ public void testAmbiguousClasses() throws Exception {

public void testAmbiguousProperties() throws Exception {
try {
/*Cat cat =*/ sharedMapper().readValue(ambiguousCatJson, Cat.class);
/*Cat cat =*/ MAPPER.readValue(ambiguousCatJson, Cat.class);
fail("Should not get here");
} catch (InvalidTypeIdException e) {
verifyException(e, "Cannot deduce unique subtype");
Expand Down Expand Up @@ -250,20 +252,20 @@ public void testDefaultImpl() throws Exception {
public void testSimpleSerialization() throws Exception {
// Given:
JavaType listOfCats = TypeFactory.defaultInstance().constructParametricType(List.class, Cat.class);
List<Cat> list = sharedMapper().readValue(arrayOfCatsJson, listOfCats);
List<Cat> list = MAPPER.readValue(arrayOfCatsJson, listOfCats);
Cat cat = list.get(0);
// When:
String json = sharedMapper().writeValueAsString(cat);
String json = MAPPER.writeValueAsString(cat);
// Then:
assertEquals(liveCatJson, json);
}

public void testListSerialization() throws Exception {
// Given:
JavaType listOfCats = TypeFactory.defaultInstance().constructParametricType(List.class, Cat.class);
List<Cat> list = sharedMapper().readValue(arrayOfCatsJson, listOfCats);
List<Cat> list = MAPPER.readValue(arrayOfCatsJson, listOfCats);
// When:
String json = sharedMapper().writeValueAsString(list);
String json = MAPPER.writeValueAsString(list);
// Then:
assertEquals(arrayOfCatsJson, json);
}
Expand Down

0 comments on commit e85f7f0

Please sign in to comment.