From eba7b84d276eade44f63aa4df3a8581b770e3402 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 15 Feb 2021 13:53:00 -0800 Subject: [PATCH] Fix #3056 --- release-notes/VERSION-2.x | 3 ++ .../deser/std/JsonNodeDeserializer.java | 39 +++++++++----- .../databind/deser/merge/NodeMergeTest.java | 52 ++++++++++++++++++- 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 3eac12d0d1..724768d382 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -14,6 +14,9 @@ Project: jackson-databind (fix contributed by Migwel@github) #3038: Two cases of incorrect error reporting about DeserializationFeature (reported by Jelle V) +#3056: MismatchedInputException: Cannot deserialize instance of + `com.fasterxml.jackson.databind.node.ObjectNode` out of VALUE_NULL token + (reported by Stexxen@github) 2.12.1 (08-Jan-2021) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java index f93054941e..3ae3fac770 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java @@ -389,18 +389,25 @@ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt, JsonNode old = node.get(key); if (old != null) { if (old instanceof ObjectNode) { - JsonNode newValue = updateObject(p, ctxt, (ObjectNode) old); - if (newValue != old) { - node.set(key, newValue); + // [databind#3056]: merging only if had Object and + // getting an Object + if (t == JsonToken.START_OBJECT) { + JsonNode newValue = updateObject(p, ctxt, (ObjectNode) old); + if (newValue != old) { + node.set(key, newValue); + } + continue; } - continue; - } - if (old instanceof ArrayNode) { - JsonNode newValue = updateArray(p, ctxt, (ArrayNode) old); - if (newValue != old) { - node.set(key, newValue); + } else if (old instanceof ArrayNode) { + // [databind#3056]: related to Object handling, ensure + // Array values also match for mergeability + if (t == JsonToken.START_ARRAY) { + JsonNode newValue = updateArray(p, ctxt, (ArrayNode) old); + if (newValue != old) { + node.set(key, newValue); + } + continue; } - continue; } } if (t == null) { // can this ever occur? @@ -436,10 +443,15 @@ protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt, default: value = deserializeAny(p, ctxt, nodeFactory); } + // 15-Feb-2021, tatu: I don't think this should have been called + // on update case (was until 2.12.2) and was simply result of + // copy-paste. + /* if (old != null) { _handleDuplicateField(p, ctxt, nodeFactory, key, node, old, value); } + */ node.set(key, value); } return node; @@ -449,8 +461,9 @@ protected final ArrayNode deserializeArray(JsonParser p, DeserializationContext final JsonNodeFactory nodeFactory) throws IOException { ArrayNode node = nodeFactory.arrayNode(); - while (true) { - JsonToken t = p.nextToken(); + JsonToken t; + + while ((t = p.nextToken()) != null) { switch (t.id()) { case JsonTokenId.ID_START_OBJECT: node.add(deserializeObject(p, ctxt, nodeFactory)); @@ -483,6 +496,8 @@ protected final ArrayNode deserializeArray(JsonParser p, DeserializationContext break; } } + // should never really get here + return node; } /** diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/merge/NodeMergeTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/merge/NodeMergeTest.java index 64ceecc4f2..85529bb5f5 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/merge/NodeMergeTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/merge/NodeMergeTest.java @@ -11,6 +11,9 @@ public class NodeMergeTest extends BaseMapTest final static ObjectMapper MAPPER = jsonMapperBuilder() // 26-Oct-2016, tatu: Make sure we'll report merge problems by default .disable(MapperFeature.IGNORE_MERGE_FOR_UNMERGEABLE) + // 15-Feb-2021, tatu: slightly related to [databind#3056], + // ensure that dup detection will not trip handling here + .enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY) .build(); static class ObjectNodeWrapper { @@ -69,8 +72,8 @@ public void testObjectDeepUpdate() throws Exception base.putNull("misc"); assertSame(base, MAPPER.readerForUpdating(base) - .readValue(aposToQuotes( - "{'props':{'value':true, 'extra':25.5, 'array' : [ 3 ]}}"))); + .readValue(a2q( +"{'props':{'value':true, 'extra':25.5, 'array' : [ 3 ]}}"))); assertEquals(2, base.size()); ObjectNode resultProps = (ObjectNode) base.get("props"); assertEquals(4, resultProps.size()); @@ -114,4 +117,49 @@ public void testArrayNodeMerge() throws Exception assertEquals(0, n.size()); assertEquals("foo", w.list.get(5).asText()); } + + // [databind#3056] + public void testUpdateObjectNodeWithNull() throws Exception + { + JsonNode src = MAPPER.readTree(a2q("{'test':{}}")); + JsonNode update = MAPPER.readTree(a2q("{'test':null}")); + + ObjectNode result = MAPPER.readerForUpdating(src) + .readValue(update, ObjectNode.class); + + assertEquals(a2q("{'test':null}"), result.toString()); + } + + public void testUpdateObjectNodeWithNumber() throws Exception + { + JsonNode src = MAPPER.readTree(a2q("{'test':{}}")); + JsonNode update = MAPPER.readTree(a2q("{'test':123}")); + + ObjectNode result = MAPPER.readerForUpdating(src) + .readValue(update, ObjectNode.class); + + assertEquals(a2q("{'test':123}"), result.toString()); + } + + public void testUpdateArrayWithNull() throws Exception + { + JsonNode src = MAPPER.readTree(a2q("{'test':[]}")); + JsonNode update = MAPPER.readTree(a2q("{'test':null}")); + + ObjectNode result = MAPPER.readerForUpdating(src) + .readValue(update, ObjectNode.class); + + assertEquals(a2q("{'test':null}"), result.toString()); + } + + public void testUpdateArrayWithString() throws Exception + { + JsonNode src = MAPPER.readTree(a2q("{'test':[]}")); + JsonNode update = MAPPER.readTree(a2q("{'test':'n/a'}")); + + ObjectNode result = MAPPER.readerForUpdating(src) + .readValue(update, ObjectNode.class); + + assertEquals(a2q("{'test':'n/a'}"), result.toString()); + } }