diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 13d37dda39..a0403cee4c 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -1918,3 +1918,7 @@ Floris Westerman (@FWest98) Joren Inghelbrecht (@jin-harmoney) * Contributed #4953: Allow clearing all caches to avoid classloader leaks (2.19.0) + +Will Paul (@dropofwill) + * Contributed #4979: Allow default enums with `@JsonCreator` + (2.19.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 573438c014..7b894cedcc 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -66,6 +66,8 @@ Project: jackson-databind (fix by Joo-Hyuk K) #4963: Serializing `Map.Entry` as Bean with `@JsonFormat.shape = Shape.OBJECT` fails on JDK 17+ +#4979: Allow default enums with `@JsonCreator` + (contributed by Will P) #4997: `ObjectNode` put methods should do null check for key #5006: Add `MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS` to prevent failure of `java.util.Optional` (de)serialization without Java 8 module 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 1a71004792..d523e7dd63 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1087,7 +1087,9 @@ public JsonDeserializer createEnumDeserializer(DeserializationContext ctxt, "Invalid `@JsonCreator` annotated Enum factory method [%s]: needs to return compatible type", factory.toString())); } - deser = EnumDeserializer.deserializerForCreator(config, enumClass, factory, valueInstantiator, creatorProps); + deser = EnumDeserializer.deserializerForCreator( + config, enumClass, factory, valueInstantiator, creatorProps, + constructEnumResolver(enumClass, config, beanDesc)); break; } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java index a2973e3930..9320560c27 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java @@ -147,20 +147,20 @@ protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive) { } /** - * @deprecated Since 2.9 - */ - @Deprecated - public EnumDeserializer(EnumResolver byNameResolver) { - this(byNameResolver, null); - } - - /** - * @deprecated Since 2.8 + * Factory method used when Enum instances are to be deserialized + * using a creator (static factory method) + * + * @return Deserializer based on given factory method + * + * @since 2.8 + * @deprecated Since 2.19 */ @Deprecated public static JsonDeserializer deserializerForCreator(DeserializationConfig config, - Class enumClass, AnnotatedMethod factory) { - return deserializerForCreator(config, enumClass, factory, null, null); + Class enumClass, AnnotatedMethod factory, + ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps) { + return deserializerForCreator(config, enumClass, factory, valueInstantiator, + creatorProps, null); } /** @@ -169,19 +169,19 @@ public static JsonDeserializer deserializerForCreator(DeserializationConfig c * * @return Deserializer based on given factory method * - * @since 2.8 + * @since 2.19 */ public static JsonDeserializer deserializerForCreator(DeserializationConfig config, Class enumClass, AnnotatedMethod factory, - ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps) + ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps, + EnumResolver byNameResolver) { if (config.canOverrideAccessModifiers()) { ClassUtil.checkAndFixAccess(factory.getMember(), config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } - return new FactoryBasedEnumDeserializer(enumClass, factory, - factory.getParameterType(0), - valueInstantiator, creatorProps); + return new FactoryBasedEnumDeserializer(enumClass, factory, factory.getParameterType(0), + valueInstantiator, creatorProps, byNameResolver); } /** diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java index 3f01110f38..09f16946a6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/FactoryBasedEnumDeserializer.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.type.LogicalType; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.EnumResolver; /** * Deserializer that uses a single-String static factory method @@ -35,6 +36,9 @@ class FactoryBasedEnumDeserializer protected final ValueInstantiator _valueInstantiator; protected final SettableBeanProperty[] _creatorProps; + // @since 2.19 + protected final Enum _defaultValue; + protected final boolean _hasArgs; /** @@ -44,8 +48,23 @@ class FactoryBasedEnumDeserializer */ private transient volatile PropertyBasedCreator _propCreator; + /** + * @since 2.8 + * @deprecated since 2.19 + */ + @Deprecated public FactoryBasedEnumDeserializer(Class cls, AnnotatedMethod f, JavaType paramType, ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps) + { + this(cls, f, paramType, valueInstantiator, creatorProps, null); + } + + /** + * @since 2.19 + */ + public FactoryBasedEnumDeserializer(Class cls, AnnotatedMethod f, JavaType paramType, + ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps, + EnumResolver enumResolver) { super(cls); _factory = f; @@ -56,6 +75,7 @@ public FactoryBasedEnumDeserializer(Class cls, AnnotatedMethod f, JavaType pa _deser = null; _valueInstantiator = valueInstantiator; _creatorProps = creatorProps; + _defaultValue = (enumResolver == null) ? null : enumResolver.getDefaultValue(); } /** @@ -70,6 +90,7 @@ public FactoryBasedEnumDeserializer(Class cls, AnnotatedMethod f) _deser = null; _valueInstantiator = null; _creatorProps = null; + _defaultValue = null; } protected FactoryBasedEnumDeserializer(FactoryBasedEnumDeserializer base, @@ -80,6 +101,7 @@ protected FactoryBasedEnumDeserializer(FactoryBasedEnumDeserializer base, _hasArgs = base._hasArgs; _valueInstantiator = base._valueInstantiator; _creatorProps = base._creatorProps; + _defaultValue = base._defaultValue; _deser = deser; } @@ -198,7 +220,14 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx } catch (Exception e) { Throwable t = ClassUtil.throwRootCauseIfIOE(e); if (t instanceof IllegalArgumentException) { - // [databind#1642]: + // [databind#4979]: unknown as default + if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { + // ... only if we DO have a default + if (_defaultValue != null) { + return _defaultValue; + } + } + // [databind#1642]: unknown as null if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { return null; } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/EnumCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/EnumCreatorTest.java index 4aaf6d8c86..2a5bacefee 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/EnumCreatorTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/EnumCreatorTest.java @@ -125,7 +125,8 @@ public JsonDeserializer findEnumDeserializer(final Class type, final Deser for (AnnotatedMethod am : factoryMethods) { final JsonCreator creator = am.getAnnotation(JsonCreator.class); if (creator != null) { - return EnumDeserializer.deserializerForCreator(config, type, am, null, null); + return EnumDeserializer.deserializerForCreator( + config, type, am, null, null, null); } } } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumDeserializationTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumDeserializationTest.java index 18a7f9108b..94814c3c30 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumDeserializationTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumDeserializationTest.java @@ -161,7 +161,7 @@ static enum EnumWithDefaultAnnoAndConstructor { } static enum StrictEnumCreator { - A, B; + A, B, @JsonEnumDefaultValue UNKNOWN; @JsonCreator public static StrictEnumCreator fromId(String value) { for (StrictEnumCreator e: values()) { @@ -453,7 +453,7 @@ public void testAllowUnknownEnumValuesReadAsNull() throws Exception assertNull(reader.forType(TestEnum.class).readValue(" 4343 ")); } - // Ability to ignore unknown Enum values: + // Ability to ignore unknown Enum values as null: // [databind#1642] @Test @@ -483,6 +483,19 @@ public void testAllowUnknownEnumValuesAsMapKeysReadAsNull() throws Exception assertTrue(result.map.containsKey(null)); } + // Ability to ignore unknown Enum values as a defined default: + + // [databind#4979] + @Test + public void testAllowUnknownEnumValuesReadAsDefaultWithCreatorMethod4979() throws Exception + { + ObjectReader reader = MAPPER.reader( + DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE); + assertEquals( + StrictEnumCreator.UNKNOWN, + reader.forType(StrictEnumCreator.class).readValue("\"NO-SUCH-VALUE\"")); + } + @Test public void testDoNotAllowUnknownEnumValuesAsMapKeysWhenReadAsNullDisabled() throws Exception {