Skip to content

Commit

Permalink
Fix #4214: support polymorphic deserialization of EnumSet (#4222)
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder authored Nov 28, 2023
1 parent 35bd198 commit 7d8692c
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 64 deletions.
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,23 @@ public class EnumSetDeserializer
extends StdDeserializer<EnumSet<?>>
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<Enum<?>> _enumDeserializer;

/**
* If element instances have polymorphic type information, this
* is the type deserializer that can handle it.
*<p>
* 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.
*
Expand Down Expand Up @@ -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;
Expand All @@ -72,11 +87,21 @@ public EnumSetDeserializer(JavaType enumType, JsonDeserializer<?> deser)
throw new IllegalArgumentException("Type "+enumType+" not Java Enum type");
}
_enumDeserializer = (JsonDeserializer<Enum<?>>) 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
Expand All @@ -96,6 +121,7 @@ protected EnumSetDeserializer(EnumSetDeserializer base,
super(base);
_enumType = base._enumType;
_enumDeserializer = (JsonDeserializer<Enum<?>>) deser;
_valueTypeDeserializer = base._valueTypeDeserializer;
_nullProvider = nuller;
_skipNullValues = NullsConstantProvider.isSkipper(nuller);
_unwrapSingle = unwrapSingle;
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

/*
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public LookupCache<Object, JavaType> forTypeFactory() {
return new LRUMap<>(16, 64);
}

@Override
public LookupCache<TypeKey, JsonSerializer<Object>> forSerializerCache(SerializationConfig config) {
return _cache;
}
Expand Down Expand Up @@ -154,6 +155,7 @@ public LookupCache<Object, JavaType> forTypeFactory() {
return _cache;
}

@Override
public LookupCache<TypeKey, JsonSerializer<Object>> forSerializerCache(SerializationConfig config) {
return new LRUMap<>(16, 64);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<MyEnum> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

This file was deleted.

0 comments on commit 7d8692c

Please sign in to comment.