diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index faecc79a15..a1ed9824aa 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -42,6 +42,9 @@ Project: jackson-databind (reported by Bartosz B) #2592: `ObjectMapper.setSerializationInclusion()` is ignored for `JsonAnyGetter` (reported by Oleksii K) +#2608: `ValueInstantiationException` when deserializing using a builder and + `UNWRAP_SINGLE_VALUE_ARRAYS` + (reported by cadrake@github) #2627: JsonIgnoreProperties(ignoreUnknown = true) does not work on field and method level (reported by robotmrv@github) #2632: Failure to resolve generic type parameters on serialization diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 1480780dbe..f83a8e25d3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -186,8 +186,8 @@ protected final Object _deserializeOther(JsonParser p, DeserializationContext ct case VALUE_NULL: return deserializeFromNull(p, ctxt); case START_ARRAY: - // these only work if there's a (delegating) creator... - return deserializeFromArray(p, ctxt); + // these only work if there's a (delegating) creator, or UNWRAP_SINGLE_ARRAY + return _deserializeFromArray(p, ctxt); case FIELD_NAME: case END_OBJECT: // added to resolve [JACKSON-319], possible related issues if (_vanillaProcessing) { @@ -567,6 +567,41 @@ protected Object deserializeFromNull(JsonParser p, DeserializationContext ctxt) return ctxt.handleUnexpectedToken(getValueType(ctxt), p); } + @Override + protected Object _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException + { + // note: cannot call `_delegateDeserializer()` since order reversed here: + JsonDeserializer delegateDeser = _arrayDelegateDeserializer; + // fallback to non-array delegate + if ((delegateDeser != null) || ((delegateDeser = _delegateDeserializer) != null)) { + Object bean = _valueInstantiator.createUsingArrayDelegate(ctxt, + delegateDeser.deserialize(p, ctxt)); + if (_injectables != null) { + injectValues(ctxt, bean); + } + return bean; + } + if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + JsonToken t = p.nextToken(); + if (t == JsonToken.END_ARRAY && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { + return null; + } + final Object value = deserialize(p, ctxt); + if (p.nextToken() != JsonToken.END_ARRAY) { + handleMissingEndArrayForSingle(p, ctxt); + } + return value; + } + if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { + JsonToken t = p.nextToken(); + if (t == JsonToken.END_ARRAY) { + return null; + } + return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null); + } + return ctxt.handleUnexpectedToken(getValueType(ctxt), p); + } + /* /********************************************************** /* Deserializing when we have to consider an active View diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java index 963126525a..e2113ac1b2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -1445,38 +1445,15 @@ public Object deserializeFromBoolean(JsonParser p, DeserializationContext ctxt) return _valueInstantiator.createFromBoolean(ctxt, value); } - public Object deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException - { - // note: cannot call `_delegateDeserializer()` since order reversed here: - JsonDeserializer delegateDeser = _arrayDelegateDeserializer; - // fallback to non-array delegate - if ((delegateDeser != null) || ((delegateDeser = _delegateDeserializer) != null)) { - Object bean = _valueInstantiator.createUsingArrayDelegate(ctxt, - delegateDeser.deserialize(p, ctxt)); - if (_injectables != null) { - injectValues(ctxt, bean); - } - return bean; - } - if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - JsonToken t = p.nextToken(); - if (t == JsonToken.END_ARRAY && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { - return null; - } - final Object value = deserialize(p, ctxt); - if (p.nextToken() != JsonToken.END_ARRAY) { - handleMissingEndArrayForSingle(p, ctxt); - } - return value; - } - if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { - JsonToken t = p.nextToken(); - if (t == JsonToken.END_ARRAY) { - return null; - } - return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null); - } - return ctxt.handleUnexpectedToken(getValueType(ctxt), p); + /** + * @deprecated Since 2.11 Should not be used: was never meant to be called by + * code other than sub-classes (implementations), and implementations details + * differ + */ + @Deprecated + public Object deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException { + // should work as subtypes ought to override this method: + return _deserializeFromArray(p, ctxt); } public Object deserializeFromEmbedded(JsonParser p, DeserializationContext ctxt) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java index 2aff0aae30..1ed76ce3b2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java @@ -197,8 +197,7 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) if (_vanillaProcessing) { return finishBuild(ctxt, vanillaDeserialize(p, ctxt, t)); } - Object builder = deserializeFromObject(p, ctxt); - return finishBuild(ctxt, builder); + return finishBuild(ctxt, deserializeFromObject(p, ctxt)); } // and then others, generally requiring use of @JsonCreator switch (p.getCurrentTokenId()) { @@ -214,8 +213,9 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) case JsonTokenId.ID_FALSE: return finishBuild(ctxt, deserializeFromBoolean(p, ctxt)); case JsonTokenId.ID_START_ARRAY: - // these only work if there's a (delegating) creator... - return finishBuild(ctxt, deserializeFromArray(p, ctxt)); + // these only work if there's a (delegating) creator, or UNWRAP_SINGLE_ARRAY + // [databind#2608]: Do NOT call `finishBuild()` as method implements it + return _deserializeFromArray(p, ctxt); case JsonTokenId.ID_FIELD_NAME: case JsonTokenId.ID_END_OBJECT: return finishBuild(ctxt, deserializeFromObject(p, ctxt)); @@ -478,6 +478,41 @@ protected final Object _deserialize(JsonParser p, return builder; } + @Override // since 2.11, custom implementation + protected Object _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException + { + // note: cannot call `_delegateDeserializer()` since order reversed here: + JsonDeserializer delegateDeser = _arrayDelegateDeserializer; + // fallback to non-array delegate + if ((delegateDeser != null) || ((delegateDeser = _delegateDeserializer) != null)) { + Object builder = _valueInstantiator.createUsingArrayDelegate(ctxt, + delegateDeser.deserialize(p, ctxt)); + if (_injectables != null) { + injectValues(ctxt, builder); + } + return finishBuild(ctxt, builder); + } + if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + JsonToken t = p.nextToken(); + if (t == JsonToken.END_ARRAY && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { + return null; + } + final Object value = deserialize(p, ctxt); + if (p.nextToken() != JsonToken.END_ARRAY) { + handleMissingEndArrayForSingle(p, ctxt); + } + return value; + } + if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) { + JsonToken t = p.nextToken(); + if (t == JsonToken.END_ARRAY) { + return null; + } + return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null); + } + return ctxt.handleUnexpectedToken(getValueType(ctxt), p); + } + /* /********************************************************** /* Deserializing when we have to consider an active View diff --git a/src/test/java/com/fasterxml/jackson/failing/BuilderWithUnwrappedSingleArray2608Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithUnwrappedSingleArray2608Test.java similarity index 90% rename from src/test/java/com/fasterxml/jackson/failing/BuilderWithUnwrappedSingleArray2608Test.java rename to src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithUnwrappedSingleArray2608Test.java index 9f4e1ae16e..55e50629f5 100644 --- a/src/test/java/com/fasterxml/jackson/failing/BuilderWithUnwrappedSingleArray2608Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/builder/BuilderWithUnwrappedSingleArray2608Test.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.failing; +package com.fasterxml.jackson.databind.deser.builder; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -70,8 +70,8 @@ public void testDeserializationAndFail() throws Exception { .build(); // Regular POJO would work: -// final String serialized = "{\"id\": 1, \"value\": {\"subValue\": \"123\"}}"; - final String serialized = "{\"id\": 1, \"value\": [ {\"subValue\": \"123\"} ]}"; +// final String serialized = "{\"value\": {\"subValue\": \"123\"}}"; + final String serialized = "{\"value\": [ {\"subValue\": \"123\"} ]}"; final ExamplePOJO2608 result = mapper.readValue(serialized, ExamplePOJO2608.class); assertNotNull(result); }