diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/EnumFeature.java b/src/main/java/com/fasterxml/jackson/databind/cfg/EnumFeature.java index 1257fa4238..a0f5016520 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/EnumFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/EnumFeature.java @@ -1,5 +1,7 @@ package com.fasterxml.jackson.databind.cfg; +import com.fasterxml.jackson.databind.SerializationFeature; + /** * New Datatype-specific configuration options related to handling of * {@link java.lang.Enum} types. @@ -8,7 +10,21 @@ */ public enum EnumFeature implements DatatypeFeature { - BOGUS_FEATURE(false); + BOGUS_FEATURE(false), + + /** + * Feature that determines standard deserialization mechanism used for + * Enum values: if enabled, Enums are assumed to have been serialized using + * index of Enum; + *

+ * Note: this feature should be symmetric to + * as {@link SerializationFeature#WRITE_ENUM_KEYS_USING_INDEX}. + *

+ * Feature is disabled by default. + * + * @since 2.15 + */ + READ_ENUM_KEYS_USING_INDEX(false); private final static int FEATURE_INDEX = DatatypeFeatures.FEATURE_INDEX_ENUM; diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java index ede71d1fa2..19fd71fdb4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.cfg.EnumFeature; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.EnumResolver; @@ -370,6 +371,14 @@ final static class EnumKD extends StdKeyDeserializer */ protected volatile EnumResolver _byToStringResolver; + /** + * Lazily constructed alternative in case there is need to + * parse using enum index method as the source. + * + * @since 2.15 + */ + protected volatile EnumResolver _byIndexResolver; + protected final Enum _enumDefaultValue; protected EnumKD(EnumResolver er, AnnotatedMethod factory) { @@ -392,6 +401,11 @@ public Object _parse(String key, DeserializationContext ctxt) throws IOException EnumResolver res = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) ? _getToStringResolver(ctxt) : _byNameResolver; Enum e = res.findEnum(key); + // If enum is found, no need to try deser using index + if (e == null && ctxt.isEnabled(EnumFeature.READ_ENUM_KEYS_USING_INDEX)) { + res = _getIndexResolver(ctxt); + e = res.findEnum(key); + } if (e == null) { if ((_enumDefaultValue != null) && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { @@ -420,6 +434,21 @@ private EnumResolver _getToStringResolver(DeserializationContext ctxt) } return res; } + + private EnumResolver _getIndexResolver(DeserializationContext ctxt) { + EnumResolver res = _byIndexResolver; + if (res == null) { + synchronized (this) { + res = _byIndexResolver; + if (res == null) { + res = EnumResolver.constructUsingIndex(ctxt.getConfig(), + _byNameResolver.getEnumClass()); + _byIndexResolver = res; + } + } + } + return res; + } } /** diff --git a/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java b/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java index 739f79d340..73bfef58e0 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java @@ -124,6 +124,31 @@ public static EnumResolver constructUsingToString(DeserializationConfig config, } /** + * Factory method for constructing resolver that maps from index of Enum.values() into + * Enum value + * + * @since 2.15 + */ + public static EnumResolver constructUsingIndex(DeserializationConfig config, Class> enumCls) { + return _constructUsingIndex(enumCls, config.getAnnotationIntrospector(), + config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)); + } + + private static EnumResolver _constructUsingIndex(Class> enumCls0, AnnotationIntrospector ai, boolean isIgnoreCase) { + final Class> enumCls = _enumClass(enumCls0); + final Enum[] enumConstants = _enumConstants(enumCls0); + HashMap> map = new HashMap<>(); + + // from last to first, so that in case of duplicate values, first wins + for (int i = enumConstants.length; --i >= 0; ) { + Enum enumValue = enumConstants[i]; + map.put(String.valueOf(i), enumValue); + } + return new EnumResolver(enumCls, enumConstants, map, + _enumDefault(ai, enumCls), isIgnoreCase, false); + } + + /** * @since 2.12 */ protected static EnumResolver _constructUsingToString(Class enumCls0, 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 394a56d838..dea111b11a 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 @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.cfg.EnumFeature; import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.exc.InvalidFormatException; @@ -654,4 +655,41 @@ public void testIssue3006() throws Exception assertEquals(Operation3006.THREE, MAPPER.readValue("3", Operation3006.class)); assertEquals(Operation3006.THREE, MAPPER.readValue(q("3"), Operation3006.class)); } + + public void testEnumFeature_EnumIndexAsKey() throws Exception { + ObjectReader reader = MAPPER.reader() + .with(EnumFeature.READ_ENUM_KEYS_USING_INDEX); + + ClassWithEnumMapKey result = reader.readValue("{\"map\": {\"0\":\"I AM FOR REAL\"}}", ClassWithEnumMapKey.class); + + assertEquals(result.map.get(TestEnum.JACKSON), "I AM FOR REAL"); + } + + public void testEnumFeature_symmetric_to_writing() throws Exception { + ClassWithEnumMapKey obj = new ClassWithEnumMapKey(); + Map objMap = new HashMap<>(); + objMap.put(TestEnum.JACKSON, "I AM FOR REAL"); + obj.map = objMap; + + String deserObj = MAPPER.writer() + .with(SerializationFeature.WRITE_ENUM_KEYS_USING_INDEX) + .writeValueAsString(obj); + + ClassWithEnumMapKey result = MAPPER.reader() + .with(EnumFeature.READ_ENUM_KEYS_USING_INDEX) + .readValue(deserObj, ClassWithEnumMapKey.class); + + assertNotSame(obj, result); + assertNotSame(obj.map, result.map); + assertEquals(result.map.get(TestEnum.JACKSON), "I AM FOR REAL"); + } + + + public void testEnumFeature_READ_ENUM_KEYS_USING_INDEX_isDisabledByDefault() { + ObjectReader READER = MAPPER.reader(); + assertFalse(READER.isEnabled(EnumFeature.READ_ENUM_KEYS_USING_INDEX)); + assertFalse(READER.without(EnumFeature.READ_ENUM_KEYS_USING_INDEX) + .isEnabled(EnumFeature.READ_ENUM_KEYS_USING_INDEX)); + } + }