diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index b18da112bf..7909bfce31 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -9,6 +9,9 @@ Project: jackson-databind #4209: Make `BeanDeserializerModifier`/`BeanSerializerModifier` implement `java.io.Serializable` (fix contributed by Muhammad K) +#4214: `EnumSet` deserialization does not work when we activate + default typing in `ObjectMapper` + (reported by @dvhvsekhar) 2.16.1 (not yet released) 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 40a6e8afdd..ff060f4f5d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1395,7 +1395,8 @@ public JsonDeserializer createCollectionDeserializer(DeserializationContext c if (contentDeser == null) { // not defined by annotation // One special type: EnumSet: if (EnumSet.class.isAssignableFrom(collectionClass)) { - deser = new EnumSetDeserializer(contentType, null); + deser = new EnumSetDeserializer(contentType, null, + contentTypeDeser); } } } 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 acf03d9336..09d45bf284 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 @@ -26,12 +26,23 @@ public class EnumSetDeserializer extends StdDeserializer> implements ContextualDeserializer { - private static final long serialVersionUID = 1L; // since 2.5 + private static final long serialVersionUID = 2L; // since 2.17 protected final JavaType _enumType; protected JsonDeserializer> _enumDeserializer; + /** + * If element instances have polymorphic type information, this + * is the type deserializer that can handle it. + *

+ * NOTE: only added in 2.17 due to new {@code DefaultType} choices + * that allow polymorphic deserialization of {@code Enum} types. + * + * @since 2.17 + */ + protected final TypeDeserializer _valueTypeDeserializer; + /** * Handler we need for dealing with nulls. * @@ -62,8 +73,12 @@ public class EnumSetDeserializer /********************************************************** */ + /** + * @since 2.17 + */ @SuppressWarnings("unchecked" ) - public EnumSetDeserializer(JavaType enumType, JsonDeserializer deser) + public EnumSetDeserializer(JavaType enumType, JsonDeserializer deser, + TypeDeserializer valueTypeDeser) { super(EnumSet.class); _enumType = enumType; @@ -72,11 +87,21 @@ public EnumSetDeserializer(JavaType enumType, JsonDeserializer deser) throw new IllegalArgumentException("Type "+enumType+" not Java Enum type"); } _enumDeserializer = (JsonDeserializer>) deser; + _valueTypeDeserializer = valueTypeDeser; _unwrapSingle = null; _nullProvider = null; _skipNullValues = false; } + /** + * @deprecated Since 2.17 + */ + @Deprecated + public EnumSetDeserializer(JavaType enumType, JsonDeserializer deser) + { + this(enumType, deser, null); + } + /** * @since 2.7 * @deprecated Since 2.10.1 @@ -96,6 +121,7 @@ protected EnumSetDeserializer(EnumSetDeserializer base, super(base); _enumType = base._enumType; _enumDeserializer = (JsonDeserializer>) deser; + _valueTypeDeserializer = base._valueTypeDeserializer; _nullProvider = nuller; _skipNullValues = NullsConstantProvider.isSkipper(nuller); _unwrapSingle = unwrapSingle; @@ -108,22 +134,30 @@ public EnumSetDeserializer withDeserializer(JsonDeserializer deser) { return new EnumSetDeserializer(this, deser, _nullProvider, _unwrapSingle); } - @Deprecated // since 2.10.1 - public EnumSetDeserializer withResolved(JsonDeserializer deser, Boolean unwrapSingle) { - return withResolved(deser, _nullProvider, unwrapSingle); - } - /** * @since 2.10.1 */ - public EnumSetDeserializer withResolved(JsonDeserializer deser, NullValueProvider nuller, - Boolean unwrapSingle) { - if ((Objects.equals(_unwrapSingle, unwrapSingle)) && (_enumDeserializer == deser) && (_nullProvider == deser)) { + public EnumSetDeserializer withResolved(JsonDeserializer deser, + TypeDeserializer valueTypeDeser, + NullValueProvider nuller, Boolean unwrapSingle) { + if ((Objects.equals(_unwrapSingle, unwrapSingle)) + && (_enumDeserializer == deser) + && (_valueTypeDeserializer == valueTypeDeser) + && (_nullProvider == deser)) { return this; } return new EnumSetDeserializer(this, deser, nuller, unwrapSingle); } + /** + * @deprecated Since 2.17 + */ + @Deprecated + public EnumSetDeserializer withResolved(JsonDeserializer deser, + NullValueProvider nuller, Boolean unwrapSingle) { + return withResolved(deser, _valueTypeDeserializer, nuller, unwrapSingle); + } + /* /********************************************************** /* Basic metadata @@ -137,12 +171,14 @@ public EnumSetDeserializer withResolved(JsonDeserializer deser, NullValueProv @Override public boolean isCachable() { // One caveat: content deserializer should prevent caching - if (_enumType.getValueHandler() != null) { + if ((_enumType.getValueHandler() != null) + // Another: polymorphic deserialization + || (_valueTypeDeserializer != null)) { return false; } return true; } - + @Override // since 2.12 public LogicalType logicalType() { return LogicalType.Collection; @@ -184,7 +220,13 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, } else { // if directly assigned, probably not yet contextual, so: deser = ctxt.handleSecondaryContextualization(deser, property, _enumType); } - return withResolved(deser, findContentNullProvider(ctxt, property, deser), unwrapSingle); + // and finally, type deserializer needs context as well + TypeDeserializer valueTypeDeser = _valueTypeDeserializer; + if (valueTypeDeser != null) { + valueTypeDeser = valueTypeDeser.forProperty(property); + } + return withResolved(deser, valueTypeDeser, + findContentNullProvider(ctxt, property, deser), unwrapSingle); } /* @@ -220,6 +262,7 @@ protected final EnumSet _deserialize(JsonParser p, DeserializationContext ctx EnumSet result) throws IOException { JsonToken t; + final TypeDeserializer typeDeser = _valueTypeDeserializer; try { while ((t = p.nextToken()) != JsonToken.END_ARRAY) { @@ -232,8 +275,10 @@ protected final EnumSet _deserialize(JsonParser p, DeserializationContext ctx continue; } value = (Enum) _nullProvider.getNullValue(ctxt); - } else { + } else if (typeDeser == null) { value = _enumDeserializer.deserialize(p, ctxt); + } else { + value = (Enum) _enumDeserializer.deserializeWithType(p, ctxt, typeDeser); } if (value != null) { result.add(value); diff --git a/src/test/java/com/fasterxml/jackson/databind/cfg/CacheProviderTest.java b/src/test/java/com/fasterxml/jackson/databind/cfg/CacheProviderTest.java index ff42d1d332..492dc13fb4 100644 --- a/src/test/java/com/fasterxml/jackson/databind/cfg/CacheProviderTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/cfg/CacheProviderTest.java @@ -121,6 +121,7 @@ public LookupCache forTypeFactory() { return new LRUMap<>(16, 64); } + @Override public LookupCache> forSerializerCache(SerializationConfig config) { return _cache; } @@ -154,6 +155,7 @@ public LookupCache forTypeFactory() { return _cache; } + @Override public LookupCache> forSerializerCache(SerializationConfig config) { return new LRUMap<>(16, 64); } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/BeanDeserializerModifier4216Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/BeanDeserializerModifier4216Test.java index 5cf9cc2235..f86aac568b 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/BeanDeserializerModifier4216Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/BeanDeserializerModifier4216Test.java @@ -48,6 +48,8 @@ private static SimpleModule getSimpleModuleWithCounter(AtomicInteger counter) { SimpleModule module = new SimpleModule(); module.setDeserializerModifier( new BeanDeserializerModifier() { + private static final long serialVersionUID = 1L; + @Override public JsonDeserializer modifyArrayDeserializer(DeserializationConfig config, ArrayType valueType, BeanDescription beanDesc, JsonDeserializer deserializer) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumSetPolymorphicDeser4214Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumSetPolymorphicDeser4214Test.java new file mode 100644 index 0000000000..d1d4cc9221 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/enums/EnumSetPolymorphicDeser4214Test.java @@ -0,0 +1,46 @@ +package com.fasterxml.jackson.databind.deser.enums; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; + +// For [databind#4214] +public class EnumSetPolymorphicDeser4214Test extends BaseMapTest +{ + static enum MyEnum { + ITEM_A, ITEM_B; + } + + static class EnumSetHolder { + public Set enumSet; // use Set instead of EnumSet for type of this + + @Override + public boolean equals(Object o) { + if (!(o instanceof EnumSetHolder)) { + return false; + } + EnumSetHolder eh = (EnumSetHolder) o; + return Objects.equals(enumSet, eh.enumSet); + } + } + + public void testPolymorphicDeserialization4214() throws Exception + { + // Need to use Default Typing to trigger issue + ObjectMapper mapper = jsonMapperBuilder() + .activateDefaultTyping(BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build(), + DefaultTyping.NON_FINAL_AND_ENUMS) + .build(); + + EnumSetHolder enumSetHolder = new EnumSetHolder(); + enumSetHolder.enumSet = EnumSet.allOf(MyEnum.class); + String json = mapper.writeValueAsString(enumSetHolder); + EnumSetHolder result = mapper.readValue(json, EnumSetHolder.class); + assertEquals(result, enumSetHolder); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifier1612Test.java b/src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifier1612Test.java index 6c707eb861..2d46c4a0c1 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifier1612Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/BeanSerializerModifier1612Test.java @@ -24,6 +24,8 @@ public Bean1612(Integer a, Integer b, Double c) { } static class Modifier1612 extends BeanSerializerModifier { + private static final long serialVersionUID = 1L; + @Override public BeanSerializerBuilder updateBuilder(SerializationConfig config, BeanDescription beanDesc, BeanSerializerBuilder builder) { diff --git a/src/test/java/com/fasterxml/jackson/failing/EnumSetSerialization4214Test.java b/src/test/java/com/fasterxml/jackson/failing/EnumSetSerialization4214Test.java deleted file mode 100644 index 1d01a545a7..0000000000 --- a/src/test/java/com/fasterxml/jackson/failing/EnumSetSerialization4214Test.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.fasterxml.jackson.failing; - -import java.util.EnumSet; -import java.util.Objects; -import java.util.Set; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; -import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; - -// For [databind#4214] -public class EnumSetSerialization4214Test extends BaseMapTest -{ - static enum MyEnum { - ITEM_A, ITEM_B; - } - - static class EnumSetHolder { - public Set enumSet; // use Set instead of EnumSet for type of this - - @Override - public boolean equals(Object o) { - if (!(o instanceof EnumSetHolder)) { - return false; - } - EnumSetHolder eh = (EnumSetHolder) o; - return Objects.equals(enumSet, eh.enumSet); - } - } - - public void testSerialization() throws Exception - { - ObjectMapper mapper = jsonMapperBuilder() - // this type information is needed in json string, for this test - .activateDefaultTyping(BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build(), - DefaultTyping.NON_FINAL_AND_ENUMS) - .build(); - - EnumSetHolder enumSetHolder = new EnumSetHolder(); - enumSetHolder.enumSet = EnumSet.allOf(MyEnum.class); - String json = mapper.writeValueAsString(enumSetHolder); - - //System.err.println("JSON = \n"+json); - - EnumSetHolder result = mapper.readValue(json, EnumSetHolder.class); - assertEquals(result, enumSetHolder); - } -}