From 25bf4ff07409b40c2c44c4c30fcd77b637ba099d Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 13 Nov 2019 22:09:58 -0800 Subject: [PATCH] Fix #2480 --- release-notes/VERSION-2.x | 1 + .../fasterxml/jackson/databind/JavaType.java | 18 ++++++--- .../deser/BasicDeserializerFactory.java | 3 +- .../deser/std/EnumSetDeserializer.java | 11 ++---- .../databind/ser/BasicSerializerFactory.java | 12 +++--- .../databind/ser/std/BeanSerializerBase.java | 7 ++-- .../jackson/databind/type/TestJavaType.java | 39 +++++++++++++++++-- 7 files changed, 64 insertions(+), 27 deletions(-) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 631cad2ffd..1a9947a717 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -8,6 +8,7 @@ Project: jackson-databind #2049: TreeTraversingParser and UTF8StreamJsonParser create contexts differently (reported by Antonio P) +#2480: Fix `JavaType.isEnumType()` to support sub-classes #2487: BeanDeserializerBuilder Protected Factory Method for Extension (contributed by Ville K) #2503: Support `@JsonSerialize(keyUsing)` and `@JsonDeserialize(keyUsing)` on Key class diff --git a/src/main/java/com/fasterxml/jackson/databind/JavaType.java b/src/main/java/com/fasterxml/jackson/databind/JavaType.java index f535b54a86..5ad0b1b195 100644 --- a/src/main/java/com/fasterxml/jackson/databind/JavaType.java +++ b/src/main/java/com/fasterxml/jackson/databind/JavaType.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.type.ResolvedType; import com.fasterxml.jackson.databind.type.TypeBindings; import com.fasterxml.jackson.databind.type.TypeFactory; +import com.fasterxml.jackson.databind.util.ClassUtil; /** * Base class for type token classes used both to contain information @@ -289,11 +290,18 @@ public boolean isConcrete() { @Override public final boolean isEnumType() { - // 29-Sep-2019, tatu: `Class.isEnum()` not enough to detect custom subtypes, - // but for some reason this fix will break couple of unit tests: - // See [databind#2480]: -// return ClassUtil.isEnumType(_class); - return _class.isEnum(); + // 29-Sep-2019, tatu: `Class.isEnum()` not enough to detect custom subtypes. + return ClassUtil.isEnumType(_class); + } + + /** + * Similar to {@link #isEnumType} except does NOT return {@code true} + * for {@link java.lang.Enum} (since that is not Enum implementation type). + * + * @since 2.11 + */ + public final boolean isEnumImplType() { + return ClassUtil.isEnumType(_class) && (_class != Enum.class); } @Override 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 7f8b3e6f9a..5325d3a481 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1326,8 +1326,7 @@ public JsonDeserializer createMapDeserializer(DeserializationContext ctxt, } else { inst = findValueInstantiator(ctxt, beanDesc); } - Class kt = keyType.getRawClass(); - if (kt == null || !ClassUtil.isEnumType(kt)) { + if (!keyType.isEnumImplType()) { throw new IllegalArgumentException("Cannot construct EnumMap; generic (key) type not available"); } deser = new EnumMapDeserializer(type, inst, null, diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java index 93eb07eab3..e5286ae781 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java @@ -11,7 +11,6 @@ import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.util.AccessPattern; -import com.fasterxml.jackson.databind.util.ClassUtil; /** * Standard deserializer for {@link EnumSet}s. @@ -28,8 +27,6 @@ public class EnumSetDeserializer protected final JavaType _enumType; - protected final Class _enumClass; - protected JsonDeserializer> _enumDeserializer; /** @@ -67,9 +64,8 @@ public EnumSetDeserializer(JavaType enumType, JsonDeserializer deser) { super(EnumSet.class); _enumType = enumType; - _enumClass = (Class) enumType.getRawClass(); // sanity check - if (!ClassUtil.isEnumType(_enumClass)) { + if (!enumType.isEnumType()) { throw new IllegalArgumentException("Type "+enumType+" not Java Enum type"); } _enumDeserializer = (JsonDeserializer>) deser; @@ -96,7 +92,6 @@ protected EnumSetDeserializer(EnumSetDeserializer base, JsonDeserializer deser, NullValueProvider nuller, Boolean unwrapSingle) { super(base); _enumType = base._enumType; - _enumClass = base._enumClass; _enumDeserializer = (JsonDeserializer>) deser; _nullProvider = nuller; _skipNullValues = NullsConstantProvider.isSkipper(nuller); @@ -250,7 +245,7 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, @SuppressWarnings("unchecked") private EnumSet constructSet() { - return EnumSet.noneOf(_enumClass); + return EnumSet.noneOf((Class) _enumType.getRawClass()); } @SuppressWarnings("unchecked") @@ -267,7 +262,7 @@ protected EnumSet handleNonArray(JsonParser p, DeserializationContext ctxt, } // First: since `null`s not allowed, slightly simpler... if (p.hasToken(JsonToken.VALUE_NULL)) { - return (EnumSet) ctxt.handleUnexpectedToken(_enumClass, p); + return (EnumSet) ctxt.handleUnexpectedToken(_enumType, p); } try { Enum value = _enumDeserializer.deserialize(p, ctxt); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java index e90b0c479d..4e7c398d20 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java @@ -418,8 +418,11 @@ protected final JsonSerializer findSerializerByPrimaryType(SerializerProvider boolean staticTyping) throws JsonMappingException { - Class raw = type.getRawClass(); - + if (type.isEnumType()) { + return buildEnumSerializer(prov.getConfig(), type, beanDesc); + } + + final Class raw = type.getRawClass(); // Then check for optional/external serializers JsonSerializer ser = findOptionalStdSerializer(prov, type, beanDesc, staticTyping); if (ser != null) { @@ -471,9 +474,6 @@ protected final JsonSerializer findSerializerByPrimaryType(SerializerProvider } return NumberSerializer.instance; } - if (ClassUtil.isEnumType(raw) && raw != Enum.class) { - return buildEnumSerializer(prov.getConfig(), type, beanDesc); - } return null; } @@ -714,7 +714,7 @@ protected JsonSerializer buildCollectionSerializer(SerializerProvider prov, // this may or may not be available (Class doesn't; type of field/method does) JavaType enumType = type.getContentType(); // and even if nominally there is something, only use if it really is enum - if (!enumType.isEnumType()) { + if (!enumType.isEnumImplType()) { // usually since it's `Enum.class` enumType = null; } ser = buildEnumSetSerializer(enumType); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java index b7eba96bbf..f9f3f3ca3b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java @@ -5,8 +5,10 @@ import java.util.*; import com.fasterxml.jackson.annotation.*; + import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.type.WritableTypeId; + import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.ObjectIdInfo; @@ -23,7 +25,6 @@ import com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator; import com.fasterxml.jackson.databind.ser.impl.WritableObjectId; import com.fasterxml.jackson.databind.util.ArrayBuilders; -import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.Converter; import com.fasterxml.jackson.databind.util.NameTransformer; @@ -420,13 +421,13 @@ public JsonSerializer createContextual(SerializerProvider provider, // Let's start with one big transmutation: Enums that are annotated // to serialize as Objects may want to revert - JsonFormat.Value format = findFormatOverrides(provider, property, handledType()); + JsonFormat.Value format = findFormatOverrides(provider, property, _handledType); JsonFormat.Shape shape = null; if ((format != null) && format.hasShape()) { shape = format.getShape(); // or, alternatively, asked to revert "back to" other representations... if ((shape != JsonFormat.Shape.ANY) && (shape != _serializationShape)) { - if (ClassUtil.isEnumType(_handledType)) { + if (_beanType.isEnumType()) { switch (shape) { case STRING: case NUMBER: diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java index ffe0b293d8..301780dd87 100644 --- a/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java +++ b/src/test/java/com/fasterxml/jackson/databind/type/TestJavaType.java @@ -25,6 +25,22 @@ static enum MyEnum2 { private MyEnum2(int value) { } } + static enum MyEnumSub { + A(1) { + @Override public String toString() { + return "a"; + } + }, + B(2) { + @Override public String toString() { + return "b"; + } + } + ; + + private MyEnumSub(int value) { } + } + // [databind#728] static class Issue728 { public C method(C input) { return null; } @@ -122,7 +138,8 @@ public void testArrayType() assertTrue(arrayT.equals(arrayT)); assertFalse(arrayT.equals(null)); - assertFalse(arrayT.equals("xyz")); + final Object bogus = "xyz"; + assertFalse(arrayT.equals(bogus)); assertTrue(arrayT.equals(ArrayType.construct(tf.constructType(String.class), null))); assertFalse(arrayT.equals(ArrayType.construct(tf.constructType(Integer.class), null))); @@ -145,14 +162,19 @@ public void testMapType() assertTrue(mapT.equals(mapT)); assertFalse(mapT.equals(null)); - assertFalse(mapT.equals("xyz")); + Object bogus = "xyz"; + assertFalse(mapT.equals(bogus)); } - + public void testEnumType() { TypeFactory tf = TypeFactory.defaultInstance(); JavaType enumT = tf.constructType(MyEnum.class); + // JDK actually works fine with "basic" Enum types... + assertTrue(enumT.getRawClass().isEnum()); assertTrue(enumT.isEnumType()); + assertTrue(enumT.isEnumImplType()); + assertFalse(enumT.hasHandlers()); assertTrue(enumT.isTypeOrSubTypeOf(MyEnum.class)); assertTrue(enumT.isTypeOrSubTypeOf(Object.class)); @@ -165,6 +187,17 @@ public void testEnumType() assertTrue(tf.constructType(MyEnum2.class).isEnumType()); assertTrue(tf.constructType(MyEnum.A.getClass()).isEnumType()); assertTrue(tf.constructType(MyEnum2.A.getClass()).isEnumType()); + + // [databind#2480] + assertFalse(tf.constructType(Enum.class).isEnumImplType()); + JavaType enumSubT = tf.constructType(MyEnumSub.B.getClass()); + assertTrue(enumSubT.isEnumType()); + assertTrue(enumSubT.isEnumImplType()); + + // and this is kind of odd twist by JDK: one might except this to return true, + // but no, sub-classes (when Enum values have overrides, and require sub-class) + // are NOT considered enums for whatever reason + assertFalse(enumSubT.getRawClass().isEnum()); } public void testClassKey()