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 e0df2d65f7..4d6d87b803 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -368,6 +368,10 @@ public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) t _handleTypedObjectId(p, ctxt, bean, id); } } + // [databind#3838]: since 2.16 Uniform handling of missing objectId + if (_objectIdReader != null && p.getCurrentToken() == JsonToken.END_OBJECT) { + ctxt.reportUnresolvedObjectId(_objectIdReader, bean); + } if (_injectables != null) { injectValues(ctxt, bean); } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/JsonIdentityInfoIdProperty3838Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/JsonIdentityInfoIdProperty3838Test.java new file mode 100644 index 0000000000..e4cdd94c8e --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/JsonIdentityInfoIdProperty3838Test.java @@ -0,0 +1,197 @@ +package com.fasterxml.jackson.databind.deser; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; + +// [databind#3838]: Difference in the handling of ObjectId-property in JsonIdentityInfo depending on the deserialization route. +public class JsonIdentityInfoIdProperty3838Test extends BaseMapTest { + + /* + /********************************************************** + /* Set Up + /********************************************************** + */ + interface ResultGetter { + String result(); + } + + @JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class) + static class SetterBased implements ResultGetter { + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String result() { + return id; + } + } + + @JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class) + static class CreatorBased implements ResultGetter { + private final String id; + + @JsonCreator + CreatorBased(@JsonProperty(value = "id") String id) { + this.id = id; + } + + public String getId() { + return id; + } + + @Override + public String result() { + return id; + } + } + + @JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class) + static class DefaultConstructorBased implements ResultGetter { + public String id; + + @Override + public String result() { + return id; + } + } + + @JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class) + static class StaticFactoryMethodBased implements ResultGetter { + private final String id; + + private StaticFactoryMethodBased(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + @JsonCreator + public static StaticFactoryMethodBased create(@JsonProperty("id") String id) { + return new StaticFactoryMethodBased(id); + } + + @Override + public String result() { + return id; + } + } + + @JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class) + static class MultiArgConstructorBased implements ResultGetter { + private final String id; + private final int value; + + @JsonCreator + MultiArgConstructorBased(@JsonProperty("id") String id, @JsonProperty("value") int value) { + this.id = id; + this.value = value; + } + + public String getId() { + return id; + } + + public int getValue() { + return value; + } + + @Override + public String result() { + return id; + } + } + + @JsonIdentityInfo( + generator = ObjectIdGenerators.PropertyGenerator.class, + property = "id" + ) + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.WRAPPER_OBJECT, + property = "type" + ) + @JsonSubTypes({ + @JsonSubTypes.Type(value = Concrete3838.class, name = "concrete_3838") + }) + static class BaseType3838 implements ResultGetter { + public String id; + + @Override + public String result() { + return id; + } + } + + @JsonTypeName("concrete_3838") + static class Concrete3838 extends BaseType3838 { + public String location; + + protected Concrete3838() {} + + public Concrete3838(String id, String loc) { + this.id = id; + location = loc; + } + } + + @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id") + static class IntSequencedBean implements ResultGetter{ + public String value; + + @Override + public String result() { + return value; + } + } + + /* + /********************************************************** + /* Test + /********************************************************** + */ + + private final ObjectMapper MAPPER = newJsonMapper(); + + final static Object[][] CLASS_AND_JSON_STRING = new Object[][]{ + {SetterBased.class, "{'id':'great'}"}, + {CreatorBased.class, "{'id':'great'}"}, + {DefaultConstructorBased.class, "{'id':'great'}"}, + {StaticFactoryMethodBased.class, "{'id':'great'}"}, + {MultiArgConstructorBased.class, "{'id':'great','value':42}"}, + {BaseType3838.class, "{'concrete_3838':{'id':'great','location':'Bangkok'}}"}, + {IntSequencedBean.class, "{'id':-1,'value':'great'}"} + }; + + public void testUniformHandlingForMissingObjectId() throws Exception { + for (Object[] classAndJsonStrEntry : CLASS_AND_JSON_STRING) { + final Class cls = (Class) classAndJsonStrEntry[0]; + final String jsonStr = (String) classAndJsonStrEntry[1]; + + // 1. throws MismatchedInputException with empty JSON object + try { + MAPPER.readValue("{}", cls); + fail("should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, + "No Object Id found for an instance of", "to", + "to assign to property 'id'"); + } + + // 2. but works with non-empty JSON object + ResultGetter nonEmptyObj = (ResultGetter) MAPPER.readValue(a2q(jsonStr), cls); + assertEquals("great", nonEmptyObj.result()); + } + } +}