Skip to content

Commit 959980e

Browse files
authored
[databind#4849] Allow standard defaultTyping with EnumSet<E> (#4857)
1 parent 141d318 commit 959980e

18 files changed

+138
-95
lines changed

release-notes/VERSION-2.x

+4-2
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@ Project: jackson-databind
3131
(reported by Eduard G)
3232
#4773: `SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS` should not apply to Maps
3333
with uncomparable keys
34-
(requested by @nathanukey)
34+
(requested by @nathanukey
35+
#4849 Not able to deserialize Enum with default typing after upgrading 2.15.4 -> 2.17.1
36+
(reported by Kornel Zemla)
3537

3638
2.18.3 (not yet released)
3739

3840
#4827: Subclassed Throwable deserialization fails since v2.18.0 - no creator
3941
index for property 'cause'
4042
(reported by @nilswieber)
4143
(fix by Joo-Hyuk K)
42-
#4844: Fix wrapped array hanlding wrt `null` by `StdDeserializer`
44+
#4844: Fix wrapped array handling wrt `null` by `StdDeserializer`
4345
(fix by Stanislav S)
4446

4547
2.18.2 (27-Nov-2024)

src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -788,8 +788,7 @@ public JsonDeserializer<?> createCollectionDeserializer(DeserializationContext c
788788
if (contentDeser == null) { // not defined by annotation
789789
// One special type: EnumSet:
790790
if (EnumSet.class.isAssignableFrom(collectionClass)) {
791-
deser = new EnumSetDeserializer(contentType, null,
792-
contentTypeDeser);
791+
deser = new EnumSetDeserializer(contentType, null);
793792
}
794793
}
795794
}

src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java

+13-56
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,6 @@ public class EnumSetDeserializer
3232

3333
protected JsonDeserializer<Enum<?>> _enumDeserializer;
3434

35-
/**
36-
* If element instances have polymorphic type information, this
37-
* is the type deserializer that can handle it.
38-
*<p>
39-
* NOTE: only added in 2.17 due to new {@code DefaultType} choices
40-
* that allow polymorphic deserialization of {@code Enum} types.
41-
*
42-
* @since 2.17
43-
*/
44-
protected final TypeDeserializer _valueTypeDeserializer;
45-
4635
/**
4736
* Handler we need for dealing with nulls.
4837
*
@@ -74,11 +63,12 @@ public class EnumSetDeserializer
7463
*/
7564

7665
/**
77-
* @since 2.17
66+
* Main constructor for the deserializer.
67+
*<p>
68+
* NOTE: was temporarily deprecated in 2.17 - 2.18, restored in 2.19
7869
*/
7970
@SuppressWarnings("unchecked" )
80-
public EnumSetDeserializer(JavaType enumType, JsonDeserializer<?> deser,
81-
TypeDeserializer valueTypeDeser)
71+
public EnumSetDeserializer(JavaType enumType, JsonDeserializer<?> deser)
8272
{
8373
super(EnumSet.class);
8474
_enumType = enumType;
@@ -87,29 +77,19 @@ public EnumSetDeserializer(JavaType enumType, JsonDeserializer<?> deser,
8777
throw new IllegalArgumentException("Type "+enumType+" not Java Enum type");
8878
}
8979
_enumDeserializer = (JsonDeserializer<Enum<?>>) deser;
90-
_valueTypeDeserializer = valueTypeDeser;
9180
_unwrapSingle = null;
9281
_nullProvider = null;
9382
_skipNullValues = false;
9483
}
9584

9685
/**
97-
* @deprecated Since 2.17
86+
* @deprecated Since 2.19 (was added in 2.17)
9887
*/
9988
@Deprecated
100-
public EnumSetDeserializer(JavaType enumType, JsonDeserializer<?> deser)
89+
public EnumSetDeserializer(JavaType enumType, JsonDeserializer<?> deser,
90+
TypeDeserializer valueTypeDeser)
10191
{
102-
this(enumType, deser, null);
103-
}
104-
105-
/**
106-
* @since 2.7
107-
* @deprecated Since 2.10.1
108-
*/
109-
@Deprecated
110-
protected EnumSetDeserializer(EnumSetDeserializer base,
111-
JsonDeserializer<?> deser, Boolean unwrapSingle) {
112-
this(base, deser, base._nullProvider, unwrapSingle);
92+
this(enumType, deser);
11393
}
11494

11595
/**
@@ -121,7 +101,6 @@ protected EnumSetDeserializer(EnumSetDeserializer base,
121101
super(base);
122102
_enumType = base._enumType;
123103
_enumDeserializer = (JsonDeserializer<Enum<?>>) deser;
124-
_valueTypeDeserializer = base._valueTypeDeserializer;
125104
_nullProvider = nuller;
126105
_skipNullValues = NullsConstantProvider.isSkipper(nuller);
127106
_unwrapSingle = unwrapSingle;
@@ -135,29 +114,18 @@ public EnumSetDeserializer withDeserializer(JsonDeserializer<?> deser) {
135114
}
136115

137116
/**
138-
* @since 2.10.1
117+
* @since 2.19
139118
*/
140119
public EnumSetDeserializer withResolved(JsonDeserializer<?> deser,
141-
TypeDeserializer valueTypeDeser,
142120
NullValueProvider nuller, Boolean unwrapSingle) {
143121
if ((Objects.equals(_unwrapSingle, unwrapSingle))
144122
&& (_enumDeserializer == deser)
145-
&& (_valueTypeDeserializer == valueTypeDeser)
146123
&& (_nullProvider == deser)) {
147124
return this;
148125
}
149126
return new EnumSetDeserializer(this, deser, nuller, unwrapSingle);
150127
}
151128

152-
/**
153-
* @deprecated Since 2.17
154-
*/
155-
@Deprecated
156-
public EnumSetDeserializer withResolved(JsonDeserializer<?> deser,
157-
NullValueProvider nuller, Boolean unwrapSingle) {
158-
return withResolved(deser, _valueTypeDeserializer, nuller, unwrapSingle);
159-
}
160-
161129
/*
162130
/**********************************************************
163131
/* Basic metadata
@@ -171,9 +139,7 @@ public EnumSetDeserializer withResolved(JsonDeserializer<?> deser,
171139
@Override
172140
public boolean isCachable() {
173141
// One caveat: content deserializer should prevent caching
174-
if ((_enumType.getValueHandler() != null)
175-
// Another: polymorphic deserialization
176-
|| (_valueTypeDeserializer != null)) {
142+
if (_enumType.getValueHandler() != null) {
177143
return false;
178144
}
179145
return true;
@@ -220,12 +186,7 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
220186
} else { // if directly assigned, probably not yet contextual, so:
221187
deser = ctxt.handleSecondaryContextualization(deser, property, _enumType);
222188
}
223-
// and finally, type deserializer needs context as well
224-
TypeDeserializer valueTypeDeser = _valueTypeDeserializer;
225-
if (valueTypeDeser != null) {
226-
valueTypeDeser = valueTypeDeser.forProperty(property);
227-
}
228-
return withResolved(deser, valueTypeDeser,
189+
return withResolved(deser,
229190
findContentNullProvider(ctxt, property, deser), unwrapSingle);
230191
}
231192

@@ -261,10 +222,8 @@ public EnumSet<?> deserialize(JsonParser p, DeserializationContext ctxt,
261222
protected final EnumSet<?> _deserialize(JsonParser p, DeserializationContext ctxt,
262223
EnumSet result) throws IOException
263224
{
264-
JsonToken t;
265-
final TypeDeserializer typeDeser = _valueTypeDeserializer;
266-
267225
try {
226+
JsonToken t;
268227
while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
269228
// What to do with nulls? Fail or ignore? Fail, for now (note: would fail if we
270229
// passed it to EnumDeserializer, too, but in general nulls should never be passed
@@ -275,10 +234,8 @@ protected final EnumSet<?> _deserialize(JsonParser p, DeserializationContext ctx
275234
continue;
276235
}
277236
value = (Enum<?>) _nullProvider.getNullValue(ctxt);
278-
} else if (typeDeser == null) {
279-
value = _enumDeserializer.deserialize(p, ctxt);
280237
} else {
281-
value = (Enum<?>) _enumDeserializer.deserializeWithType(p, ctxt, typeDeser);
238+
value = _enumDeserializer.deserialize(p, ctxt);
282239
}
283240
if (value != null) {
284241
result.add(value);

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsArrayTypeDeserializer.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,9 @@ protected String _locateTypeId(JsonParser p, DeserializationContext ctxt) throws
146146
}
147147
return id;
148148
}
149-
ctxt.reportWrongTokenException(baseType(), JsonToken.START_ARRAY,
150-
"need Array value to contain `As.WRAPPER_ARRAY` type information for class "+baseTypeName());
151-
return null;
149+
ctxt.reportWrongTokenException(baseType(), JsonToken.START_ARRAY,
150+
"need Array value to contain `As.WRAPPER_ARRAY` type information for class "+baseTypeName());
151+
return null;
152152
}
153153
// And then type id as a String
154154
JsonToken t = p.nextToken();

src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java

+21-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.IOException;
44
import java.util.Collection;
5+
import java.util.EnumSet;
56
import java.util.Iterator;
67

78
import com.fasterxml.jackson.core.*;
@@ -26,6 +27,15 @@ public class CollectionSerializer
2627
{
2728
private static final long serialVersionUID = 1L;
2829

30+
/**
31+
* Flag that indicates that we may need to check for EnumSet dynamically
32+
* during serialization: problem being that we can't always do it statically.
33+
* But we can figure out when there is a possibility wrt type signature we get.
34+
*
35+
* @since 2.18.3
36+
*/
37+
private final boolean _maybeEnumSet;
38+
2939
/*
3040
/**********************************************************
3141
/* Life-cycle
@@ -38,22 +48,17 @@ public class CollectionSerializer
3848
public CollectionSerializer(JavaType elemType, boolean staticTyping, TypeSerializer vts,
3949
JsonSerializer<Object> valueSerializer) {
4050
super(Collection.class, elemType, staticTyping, vts, valueSerializer);
41-
}
42-
43-
/**
44-
* @deprecated since 2.6
45-
*/
46-
@Deprecated // since 2.6
47-
public CollectionSerializer(JavaType elemType, boolean staticTyping, TypeSerializer vts,
48-
BeanProperty property, JsonSerializer<Object> valueSerializer) {
49-
// note: assumption is 'property' is always passed as null
50-
this(elemType, staticTyping, vts, valueSerializer);
51+
// Unfortunately we can't check for EnumSet statically (if type indicated it,
52+
// we'd have constructed `EnumSetSerializer` instead). But we can check that
53+
// element type could possibly be an Enum.
54+
_maybeEnumSet = elemType.isEnumType() || elemType.isJavaLangObject();
5155
}
5256

5357
public CollectionSerializer(CollectionSerializer src,
5458
BeanProperty property, TypeSerializer vts, JsonSerializer<?> valueSerializer,
5559
Boolean unwrapSingle) {
5660
super(src, property, vts, valueSerializer, unwrapSingle);
61+
_maybeEnumSet = src._maybeEnumSet;
5762
}
5863

5964
@Override
@@ -120,7 +125,9 @@ public void serializeContents(Collection<?> value, JsonGenerator g, SerializerPr
120125
return;
121126
}
122127
PropertySerializerMap serializers = _dynamicSerializers;
123-
final TypeSerializer typeSer = _valueTypeSerializer;
128+
// [databind#4849]/[databind#4214]: need to check for EnumSet
129+
final TypeSerializer typeSer = (_maybeEnumSet && value instanceof EnumSet<?>)
130+
? null : _valueTypeSerializer;
124131

125132
int i = 0;
126133
try {
@@ -158,7 +165,9 @@ public void serializeContentsUsing(Collection<?> value, JsonGenerator g, Seriali
158165
{
159166
Iterator<?> it = value.iterator();
160167
if (it.hasNext()) {
161-
TypeSerializer typeSer = _valueTypeSerializer;
168+
// [databind#4849]/[databind#4214]: need to check for EnumSet
169+
final TypeSerializer typeSer = (_maybeEnumSet && value instanceof EnumSet<?>)
170+
? null : _valueTypeSerializer;
162171
int i = 0;
163172
do {
164173
Object elem = it.next();

src/main/java/com/fasterxml/jackson/databind/ser/std/EnumSetSerializer.java

+6-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public EnumSetSerializer(EnumSetSerializer src,
2626

2727
@Override
2828
public EnumSetSerializer _withValueTypeSerializer(TypeSerializer vts) {
29-
// no typing for enums (always "hard" type)
29+
// no typing for enum elements (always strongly typed), so don't change
3030
return this;
3131
}
3232

@@ -48,7 +48,7 @@ public boolean hasSingleElement(EnumSet<? extends Enum<?>> value) {
4848
}
4949

5050
@Override
51-
public final void serialize(EnumSet<? extends Enum<?>> value, JsonGenerator gen,
51+
public void serialize(EnumSet<? extends Enum<?>> value, JsonGenerator gen,
5252
SerializerProvider provider) throws IOException
5353
{
5454
final int len = value.size();
@@ -70,15 +70,13 @@ public void serializeContents(EnumSet<? extends Enum<?>> value, JsonGenerator ge
7070
SerializerProvider provider)
7171
throws IOException
7272
{
73+
gen.assignCurrentValue(value);
7374
JsonSerializer<Object> enumSer = _elementSerializer;
74-
/* Need to dynamically find instance serializer; unfortunately
75-
* that seems to be the only way to figure out type (no accessors
76-
* to the enum class that set knows)
77-
*/
75+
// Need to dynamically find instance serializer; unfortunately
76+
// that seems to be the only way to figure out type (no accessors
77+
// to the enum class that set knows)
7878
for (Enum<?> en : value) {
7979
if (enumSer == null) {
80-
// 12-Jan-2010, tatu: Since enums cannot be polymorphic, let's
81-
// not bother with typed serializer variant here
8280
enumSer = provider.findContentValueSerializer(en.getDeclaringClass(), _property);
8381
}
8482
enumSer.serialize(en, gen, provider);

src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ public void jsonMapperRebuildTest()
283283

284284
JsonMapper m3 = m2.rebuild()
285285
.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
286-
.enumNamingStrategy(EnumNamingStrategies.CamelCaseStrategy.INSTANCE)
286+
.enumNamingStrategy(EnumNamingStrategies.UPPER_CAMEL_CASE)
287287
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
288288
.enable(EnumFeature.WRITE_ENUMS_TO_LOWERCASE)
289289
.build();

src/test/java/com/fasterxml/jackson/databind/deser/creators/ValueInstantiatorTest.java

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
1313
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
1414
import com.fasterxml.jackson.databind.module.SimpleModule;
15-
import com.fasterxml.jackson.databind.type.TypeFactory;
1615

1716
import static org.junit.jupiter.api.Assertions.*;
1817

0 commit comments

Comments
 (0)