diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index f5cb2cd90b..ccd4c4d33a 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -12,6 +12,9 @@ No changes since 2.15 #3894: Only avoid Records fields detection for deserialization (contributed by Sim Y-T) +#3913: Issue with deserialization when there are unexpected properties (due + to null `StreamReadConstraints`) + (reported by @sbertault) 2.15.0 (23-Apr-2023) 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 6a16a75d51..f2487ff713 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -454,7 +454,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri // polymorphic? if (bean.getClass() != _beanType.getRawClass()) { - return handlePolymorphic(p, ctxt, bean, unknown); + return handlePolymorphic(p, ctxt, p.streamReadConstraints(), bean, unknown); } if (unknown != null) { // nope, just extra unknown stuff... bean = handleUnknownProperties(ctxt, bean, unknown); @@ -538,7 +538,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri if (unknown != null) { // polymorphic? if (bean.getClass() != _beanType.getRawClass()) { // lgtm [java/dereferenced-value-may-be-null] - return handlePolymorphic(null, ctxt, bean, unknown); + return handlePolymorphic(null, ctxt, p.streamReadConstraints(), bean, unknown); } // no, just some extra unknown properties return handleUnknownProperties(ctxt, bean, unknown); 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 7f1b859f40..cb36d2dc89 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -1735,10 +1735,33 @@ protected void handleIgnoredProperty(JsonParser p, DeserializationContext ctxt, * @param p (optional) If not null, parser that has more properties to handle * (in addition to buffered properties); if null, all properties are passed * in buffer + * @deprecated use {@link #handlePolymorphic(JsonParser, DeserializationContext, StreamReadConstraints, Object, TokenBuffer)} */ + @Deprecated protected Object handlePolymorphic(JsonParser p, DeserializationContext ctxt, Object bean, TokenBuffer unknownTokens) throws IOException + { + final StreamReadConstraints streamReadConstraints = p == null ? + StreamReadConstraints.defaults() : p.streamReadConstraints(); + return handlePolymorphic(p, ctxt, streamReadConstraints, bean, unknownTokens); + } + + /** + * Method called in cases where we may have polymorphic deserialization + * case: that is, type of Creator-constructed bean is not the type + * of deserializer itself. It should be a sub-class or implementation + * class; either way, we may have more specific deserializer to use + * for handling it. + * + * @param p (optional) If not null, parser that has more properties to handle + * (in addition to buffered properties); if null, all properties are passed + * in buffer + * @since 2.15.1 + */ + protected Object handlePolymorphic(JsonParser p, DeserializationContext ctxt, + StreamReadConstraints streamReadConstraints, Object bean, TokenBuffer unknownTokens) + throws IOException { // First things first: maybe there is a more specific deserializer available? JsonDeserializer subDeser = _findSubclassDeserializer(ctxt, bean, unknownTokens); @@ -1746,7 +1769,7 @@ protected Object handlePolymorphic(JsonParser p, DeserializationContext ctxt, if (unknownTokens != null) { // need to add END_OBJECT marker first unknownTokens.writeEndObject(); - JsonParser p2 = unknownTokens.asParser(p.streamReadConstraints()); + JsonParser p2 = unknownTokens.asParser(streamReadConstraints); p2.nextToken(); // to get to first data field bean = subDeser.deserialize(p2, ctxt, bean); } 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 50e2dce169..628e01da27 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BuilderBasedDeserializer.java @@ -395,7 +395,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, } // polymorphic? if (builder.getClass() != _beanType.getRawClass()) { - return handlePolymorphic(p, ctxt, builder, unknown); + return handlePolymorphic(p, ctxt, p.streamReadConstraints(), builder, unknown); } if (unknown != null) { // nope, just extra unknown stuff... builder = handleUnknownProperties(ctxt, builder, unknown); @@ -440,7 +440,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, if (unknown != null) { // polymorphic? if (builder.getClass() != _beanType.getRawClass()) { - return handlePolymorphic(null, ctxt, builder, unknown); + return handlePolymorphic(null, ctxt, p.streamReadConstraints(), builder, unknown); } // no, just some extra unknown properties return handleUnknownProperties(ctxt, builder, unknown); @@ -669,7 +669,7 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, continue; // never gets here } if (builder.getClass() != _beanType.getRawClass()) { - return handlePolymorphic(p, ctxt, builder, tokens); + return handlePolymorphic(p, ctxt, p.streamReadConstraints(), builder, tokens); } return deserializeWithUnwrapped(p, ctxt, builder, tokens); } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/Issue3913DeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/Issue3913DeserTest.java new file mode 100644 index 0000000000..d7425cf93a --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/Issue3913DeserTest.java @@ -0,0 +1,88 @@ +package com.fasterxml.jackson.databind.deser; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.List; + +public class Issue3913DeserTest extends BaseMapTest +{ + // [databind#3913] + static class MyResponse { + List list; + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + } + + interface Base { + + String getType(); + + String getMissingInJson(); + + @JsonCreator + static Base unmarshall( + @JsonProperty("missingInJson") String missingInJson, + @JsonProperty("type") String type + ) { + switch (type) { + case "impl": + return new Impl(type, missingInJson); + default: + return null; + } + } + } + + final static class Impl implements Base { + private String type; + private String missingInJson; + + public Impl() { + } + + public Impl(String type, String missingInJson) { + this.type = type; + this.missingInJson = missingInJson; + } + + @Override public String getType() { + return type; + } + + @Override public String getMissingInJson() { + return missingInJson; + } + + public void setType(String type) { + this.type = type; + } + + public void setMissingInJson(String missingInJson) { + this.missingInJson = missingInJson; + } + } + + // [databind#3913] + public void testDeserialization() throws JsonProcessingException { + ObjectMapper mapper = jsonMapperBuilder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + String rawResponse = "{\"list\":[{\"type\":\"impl\",\"unmappedKey\":\"unusedValue\"}]}"; + MyResponse myResponse = mapper.readValue(rawResponse, MyResponse.class); + assertNotNull(myResponse); + assertEquals(1, myResponse.list.size()); + assertEquals("impl", myResponse.list.get(0).getType()); + assertNull(myResponse.list.get(0).getMissingInJson()); + } +}