From c084b8a46ad47a213a474a897a789704abd3a513 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 1 Dec 2022 19:16:29 -0800 Subject: [PATCH] Fix #3654 --- release-notes/VERSION-2.x | 1 + .../deser/BasicDeserializerFactory.java | 28 +++++++++++++------ .../ImplicitParamsForCreatorTest.java | 28 +++++++++++++++++++ 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 4b243ce070..6fa4d36d9f 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -6,6 +6,7 @@ Project: jackson-databind 2.15.0 (not yet released) +#3654: Infer `@JsonCreator(mode = Mode.DELEGATING)` from use of `@JsonValue`) #3676: Allow use of `@JsonCreator(mode = Mode.PROPERTIES)` creator for POJOs with"empty String" coercion #3680: Timestamp in classes inside jar showing 02/01/1980 diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index a152cf5535..2d19d06bdc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -498,7 +498,8 @@ protected void _addImplicitConstructorCreators(DeserializationContext ctxt, // some single-arg factory methods (String, number) are auto-detected if (argCount == 1) { final BeanPropertyDefinition propDef = candidate.propertyDef(0); - final boolean useProps = preferPropsBased || _checkIfCreatorPropertyBased(intr, ctor, propDef); + final boolean useProps = preferPropsBased + || _checkIfCreatorPropertyBased(beanDesc, intr, ctor, propDef); if (useProps) { SettableBeanProperty[] properties = new SettableBeanProperty[1]; @@ -705,7 +706,7 @@ protected void _addImplicitFactoryCreators(DeserializationContext ctxt, continue; // 2 and more args? Must be explicit, handled earlier } BeanPropertyDefinition argDef = candidate.propertyDef(0); - boolean useProps = _checkIfCreatorPropertyBased(intr, factory, argDef); + boolean useProps = _checkIfCreatorPropertyBased(beanDesc, intr, factory, argDef); if (!useProps) { // not property based but delegating /*boolean added=*/ _handleSingleArgumentCreator(creators, factory, false, vchecker.isCreatorVisible(factory)); @@ -972,12 +973,16 @@ protected void _addExplicitAnyCreator(DeserializationContext ctxt, if (!useProps && (paramDef != null)) { // One more thing: if implicit name matches property with a getter // or field, we'll consider it property-based as well - - // 25-May-2018, tatu: as per [databind#2051], looks like we have to get - // not implicit name, but name with possible strategy-based-rename + + // 01-Dec-2022, tatu: [databind#3654] Consider `@JsonValue` to strongly + // hint at delegation-based + if (beanDesc.findJsonValueAccessor() == null) { + // 25-May-2018, tatu: as per [databind#2051], looks like we have to get + // not implicit name, but name with possible strategy-based-rename // paramName = candidate.findImplicitParamName(0); - paramName = candidate.paramName(0); - useProps = (paramName != null) && paramDef.couldSerialize(); + paramName = candidate.paramName(0); + useProps = (paramName != null) && paramDef.couldSerialize(); + } } } } @@ -1000,7 +1005,8 @@ protected void _addExplicitAnyCreator(DeserializationContext ctxt, } } - private boolean _checkIfCreatorPropertyBased(AnnotationIntrospector intr, + private boolean _checkIfCreatorPropertyBased(BeanDescription beanDesc, + AnnotationIntrospector intr, AnnotatedWithParams creator, BeanPropertyDefinition propDef) { // If explicit name, or inject id, property-based @@ -1008,6 +1014,12 @@ private boolean _checkIfCreatorPropertyBased(AnnotationIntrospector intr, || (intr.findInjectableValue(creator.getParameter(0)) != null)) { return true; } + // 01-Dec-2022, tatu: [databind#3654] Consider `@JsonValue` to strongly + // hint at delegation-based + if (beanDesc.findJsonValueAccessor() != null) { + return false; + } + if (propDef != null) { // One more thing: if implicit name matches property with a getter // or field, we'll consider it property-based as well diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitParamsForCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitParamsForCreatorTest.java index c17003a198..84fb815448 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitParamsForCreatorTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/ImplicitParamsForCreatorTest.java @@ -1,5 +1,7 @@ package com.fasterxml.jackson.databind.deser.creators; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; @@ -45,6 +47,21 @@ public Bean2932(/*@com.fasterxml.jackson.annotation.JsonProperty("paramName0")*/ } } + // [databind#3654]: infer "DELEGATING" style from `@JsonValue` + static class XY3654 { + public int paramName0; // has to be public to misdirect + + @JsonCreator + public XY3654(int paramName0) { + this.paramName0 = paramName0; + } + + @JsonValue + public int serializedAs() { + return paramName0; + } + } + /* /********************************************************** /* Test methods @@ -72,4 +89,15 @@ public void testJsonCreatorWithOtherAnnotations() throws Exception assertEquals(1, bean._a); assertEquals(2, bean._b); } + + // [databind#3654] + public void testDelegatingInferFromJsonValue() throws Exception + { + // First verify serialization via `@JsonValue` + assertEquals("123", MAPPER.writeValueAsString(new XY3654(123))); + + // And then deser, should infer despite existence of "matching" property + XY3654 result = MAPPER.readValue("345", XY3654.class); + assertEquals(345, result.paramName0); + } }