Skip to content

Commit 65ccc2c

Browse files
authored
Major Enum-serialization refactoring (#5438)
1 parent 0f410f1 commit 65ccc2c

File tree

7 files changed

+101
-98
lines changed

7 files changed

+101
-98
lines changed

src/main/java/tools/jackson/databind/ser/jdk/EnumSerializer.java

Lines changed: 39 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tools.jackson.databind.ser.jdk;
22

33
import java.util.LinkedHashSet;
4+
import java.util.List;
45
import java.util.Objects;
56
import java.util.Set;
67

@@ -11,12 +12,13 @@
1112
import tools.jackson.databind.*;
1213
import tools.jackson.databind.annotation.JacksonStdImpl;
1314
import tools.jackson.databind.cfg.EnumFeature;
14-
import tools.jackson.databind.introspect.AnnotatedClass;
15-
import tools.jackson.databind.introspect.EnumNamingStrategyFactory;
15+
import tools.jackson.databind.cfg.MapperConfig;
1616
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
1717
import tools.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
1818
import tools.jackson.databind.ser.std.StdScalarSerializer;
19+
import tools.jackson.databind.util.EnumDefinition;
1920
import tools.jackson.databind.util.EnumValues;
21+
import tools.jackson.databind.util.EnumValuesToWrite;
2022

2123
/**
2224
* Standard serializer used for {@link java.lang.Enum} types.
@@ -29,62 +31,45 @@ public class EnumSerializer
2931
extends StdScalarSerializer<Enum<?>>
3032
{
3133
/**
32-
* This map contains pre-resolved values (since there are ways to customize
33-
* actual String constants to use) to use as serializations.
34+
* Container for dynamically resolved serializations for the type.
3435
*/
35-
protected final EnumValues _values;
36+
protected final EnumValuesToWrite _enumValuesToWrite;
3637

3738
/**
3839
* Flag that is set if we statically know serialization choice between
3940
* index and textual format (null if it needs to be dynamically checked).
4041
*/
4142
protected final Boolean _serializeAsIndex;
4243

43-
/**
44-
* Map with key as converted property class defined implementation of {@link EnumNamingStrategy}
45-
* and with value as Enum names collected using <code>Enum.name()</code>.
46-
*/
47-
protected final EnumValues _valuesByEnumNaming;
48-
49-
/**
50-
* Map that contains pre-resolved values for {@link Enum#toString} to use for serialization,
51-
* while respecting {@link com.fasterxml.jackson.annotation.JsonProperty}
52-
* and {@link tools.jackson.databind.cfg.EnumFeature#WRITE_ENUMS_TO_LOWERCASE}.
53-
*/
54-
protected final EnumValues _valuesByToString;
55-
5644
/*
5745
/**********************************************************************
5846
/* Life-cycle
5947
/**********************************************************************
6048
*/
6149

62-
public EnumSerializer(EnumValues v, Boolean serializeAsIndex, EnumValues valuesByEnumNaming,
63-
EnumValues valuesByToString)
50+
public EnumSerializer(EnumValuesToWrite enumValuesToWrite, Boolean serializeAsIndex)
6451
{
65-
super(v.getEnumClass(), false);
66-
_values = v;
52+
super(enumValuesToWrite.enumClass(), false);
53+
_enumValuesToWrite = enumValuesToWrite;
6754
_serializeAsIndex = serializeAsIndex;
68-
_valuesByEnumNaming = valuesByEnumNaming;
69-
_valuesByToString = valuesByToString;
7055
}
7156

7257
/**
7358
* Factory method used by {@link tools.jackson.databind.ser.BasicSerializerFactory}
7459
* for constructing serializer instance of Enum types.
7560
*/
76-
@SuppressWarnings("unchecked")
7761
public static EnumSerializer construct(Class<?> enumClass, SerializationConfig config,
7862
BeanDescription beanDesc, JsonFormat.Value format)
7963
{
8064
// 08-Apr-2015, tatu: As per [databind#749], we cannot statically determine
8165
// between name() and toString(), need to construct `EnumValues` with names,
8266
// handle toString() case dynamically (for example)
83-
EnumValues v = EnumValues.constructFromName(config, beanDesc.getClassInfo());
84-
EnumValues valuesByEnumNaming = constructEnumNamingStrategyValues(config, (Class<Enum<?>>) enumClass, beanDesc.getClassInfo());
85-
EnumValues valuesByToString = EnumValues.constructFromToString(config, beanDesc.getClassInfo());
67+
// 26-Nov-2025, tatu: Further refactoring post-[databind#5432] to deprecate
68+
// `EnumValues`, replaced with `EnumValuesToWrite`
69+
EnumValuesToWrite writer = EnumDefinition.construct(config, beanDesc.getClassInfo())
70+
.valuesToWrite(config);
8671
Boolean serializeAsIndex = _isShapeWrittenUsingIndex(enumClass, format, true, null);
87-
return new EnumSerializer(v, serializeAsIndex, valuesByEnumNaming, valuesByToString);
72+
return new EnumSerializer(writer, serializeAsIndex);
8873
}
8974

9075
/**
@@ -103,8 +88,7 @@ public ValueSerializer<?> createContextual(SerializationContext ctxt,
10388
Boolean serializeAsIndex = _isShapeWrittenUsingIndex(type,
10489
format, false, _serializeAsIndex);
10590
if (!Objects.equals(serializeAsIndex, _serializeAsIndex)) {
106-
return new EnumSerializer(_values, serializeAsIndex,
107-
_valuesByEnumNaming, _valuesByToString);
91+
return new EnumSerializer(_enumValuesToWrite, serializeAsIndex);
10892
}
10993
}
11094
return this;
@@ -116,7 +100,12 @@ public ValueSerializer<?> createContextual(SerializationContext ctxt,
116100
/**********************************************************************
117101
*/
118102

119-
public EnumValues getEnumValues() { return _values; }
103+
@Deprecated // @since 3.1
104+
public EnumValues getEnumValues() {
105+
// 26-Nov-2025, tatu: Unfortunate, but can't really support getting
106+
// such value, so better fail flamboyantly instead of quietly
107+
throw new UnsupportedOperationException();
108+
}
120109

121110
/*
122111
/**********************************************************************
@@ -128,21 +117,17 @@ public ValueSerializer<?> createContextual(SerializationContext ctxt,
128117
public final void serialize(Enum<?> en, JsonGenerator g, SerializationContext ctxt)
129118
throws JacksonException
130119
{
131-
if (_valuesByEnumNaming != null) {
132-
g.writeString(_valuesByEnumNaming.serializedValueFor(en));
133-
return;
134-
}
135120
// Serialize as index?
136121
if (_serializeAsIndex(ctxt)) {
137122
g.writeNumber(en.ordinal());
138123
return;
139124
}
140-
// [databind#749]: or via toString()?
125+
final MapperConfig<?> config = ctxt.getConfig();
141126
if (ctxt.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)) {
142-
g.writeString(_valuesByToString.serializedValueFor(en));
127+
g.writeString(_enumValuesToWrite.enumValueFromToString(config, en));
143128
return;
144-
}
145-
g.writeString(_values.serializedValueFor(en));
129+
}
130+
g.writeString(_enumValuesToWrite.enumValueFromName(config, en));
146131
}
147132

148133
/*
@@ -154,28 +139,30 @@ public final void serialize(Enum<?> en, JsonGenerator g, SerializationContext ct
154139
@Override
155140
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
156141
{
157-
SerializationContext serializers = visitor.getContext();
158-
if (_serializeAsIndex(serializers)) {
142+
SerializationContext ctxt = visitor.getContext();
143+
if (_serializeAsIndex(ctxt)) {
159144
visitIntFormat(visitor, typeHint, JsonParser.NumberType.INT);
160145
return;
161146
}
162147
JsonStringFormatVisitor stringVisitor = visitor.expectStringFormat(typeHint);
163148
if (stringVisitor != null) {
164-
Set<String> enums = new LinkedHashSet<String>();
149+
Set<String> enumStrings = new LinkedHashSet<>();
165150

166-
// Use toString()?
167-
if ((serializers != null) &&
168-
serializers.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)) {
169-
for (SerializableString value : _valuesByToString.values()) {
170-
enums.add(value.getValue());
151+
List<Enum<?>> enums = _enumValuesToWrite.enums();
152+
if (_serializeAsIndex(ctxt)) {
153+
for (Enum<?> en : enums) {
154+
enumStrings.add(String.valueOf(en.ordinal()));
171155
}
172156
} else {
173-
// No, serialize using name() or explicit overrides
174-
for (SerializableString value : _values.values()) {
175-
enums.add(value.getValue());
157+
final MapperConfig<?> config = ctxt.getConfig();
158+
SerializableString[] values = ctxt.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)
159+
? _enumValuesToWrite.allEnumValuesFromToString(config)
160+
: _enumValuesToWrite.allEnumValuesFromName(config);
161+
for (SerializableString sstr : values) {
162+
enumStrings.add(sstr.getValue());
176163
}
177164
}
178-
stringVisitor.enumTypes(enums);
165+
stringVisitor.enumTypes(enumStrings);
179166
}
180167
}
181168

@@ -222,17 +209,4 @@ protected static Boolean _isShapeWrittenUsingIndex(Class<?> enumClass,
222209
"Unsupported serialization shape (%s) for Enum %s, not supported as %s annotation",
223210
shape, enumClass.getName(), (fromClass? "class" : "property")));
224211
}
225-
226-
/**
227-
* Factory method used to resolve an instance of {@link EnumValues}
228-
* with {@link EnumNamingStrategy} applied for the target class.
229-
*/
230-
protected static EnumValues constructEnumNamingStrategyValues(SerializationConfig config, Class<Enum<?>> enumClass,
231-
AnnotatedClass annotatedClass) {
232-
Object namingDef = config.getAnnotationIntrospector().findEnumNamingStrategy(config, annotatedClass);
233-
EnumNamingStrategy enumNamingStrategy = EnumNamingStrategyFactory.createEnumNamingStrategyInstance(
234-
namingDef, config.canOverrideAccessModifiers(), config.getEnumNamingStrategy());
235-
return enumNamingStrategy == null ? null : EnumValues.constructUsingEnumNamingStrategy(
236-
config, annotatedClass, enumNamingStrategy);
237-
}
238212
}

src/main/java/tools/jackson/databind/ser/jdk/JDKKeySerializers.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,11 @@ public void serialize(Object value, JsonGenerator g, SerializationContext ctxt)
287287
// 26-Nov-2025, tatu: In 3.0 order was opposite (TO_STRING first,
288288
// then INDEX); changed in 3.1
289289
if (ctxt.isEnabled(EnumFeature.WRITE_ENUM_KEYS_USING_INDEX)) {
290-
// 14-Sep-2019, tatu: [databind#2129] Use this specific feature
291290
g.writeName(String.valueOf(en.ordinal()));
292291
} else if (ctxt.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)) {
293-
g.writeName(_valuesToWrite.fromToString(ctxt.getConfig(), en));
292+
g.writeName(_valuesToWrite.enumValueFromToString(ctxt.getConfig(), en));
294293
} else {
295-
g.writeName(_valuesToWrite.fromName(ctxt.getConfig(), en));
294+
g.writeName(_valuesToWrite.enumValueFromName(ctxt.getConfig(), en));
296295
}
297296
}
298297
}

src/main/java/tools/jackson/databind/util/EnumValues.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010

1111
/**
1212
* Helper class used for storing String serializations of {@code Enum}s,
13-
* to match to/from external representations.
13+
* to match to external representations.
14+
*
15+
* @deprecated Since 3.1 should no longer be used (replaced by {@link EnumValuesToWrite}).
1416
*/
17+
@Deprecated
1518
public final class EnumValues
1619
implements java.io.Serializable
1720
{
@@ -31,14 +34,6 @@ private EnumValues(Class<Enum<?>> enumClass, SerializableString[] textual)
3134
_textual = textual;
3235
}
3336

34-
/**
35-
* NOTE: do NOT call this if configuration may change, and choice between toString()
36-
* and name() might change dynamically.
37-
*
38-
* @deprecated Since 3.1 call {@link #constructFromName} or {@link #constructFromToString}
39-
* instead.
40-
*/
41-
@Deprecated // since 3.1
4237
public static EnumValues construct(SerializationConfig config, AnnotatedClass enumClass) {
4338
if (config.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)) {
4439
return constructFromToString(config, enumClass);

src/main/java/tools/jackson/databind/util/EnumValuesToWrite.java

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package tools.jackson.databind.util;
22

3+
import java.util.Arrays;
4+
import java.util.List;
35
import java.util.function.Function;
46

57
import tools.jackson.core.SerializableString;
@@ -47,7 +49,21 @@ public static EnumValuesToWrite construct(MapperConfig<?> config,
4749
enumNamingStrategy, enumConstants, explicitNames);
4850
}
4951

50-
public SerializableString fromName(MapperConfig<?> config, Enum<?> en) {
52+
@SuppressWarnings("unchecked")
53+
public Class<Enum<?>> enumClass() {
54+
Class<?> cls = _annotatedClass.getRawType();
55+
return (Class<Enum<?>>) cls;
56+
}
57+
58+
public List<Enum<?>> enums() {
59+
return Arrays.asList(_enumConstants);
60+
}
61+
62+
public SerializableString enumValueFromName(MapperConfig<?> config, Enum<?> en) {
63+
return allEnumValuesFromName(config)[en.ordinal()];
64+
}
65+
66+
public SerializableString[] allEnumValuesFromName(MapperConfig<?> config) {
5167
SerializableString[] strs;
5268
if (config.isEnabled(EnumFeature.WRITE_ENUMS_TO_LOWERCASE)) {
5369
if ((strs = _enumNamesLC) == null) {
@@ -62,21 +78,29 @@ public SerializableString fromName(MapperConfig<?> config, Enum<?> en) {
6278
false);
6379
}
6480
}
65-
return strs[en.ordinal()];
81+
return strs;
82+
}
83+
84+
public SerializableString enumValueFromToString(MapperConfig<?> config, Enum<?> en) {
85+
return allEnumValuesFromToString(config)[en.ordinal()];
6686
}
6787

68-
public SerializableString fromToString(MapperConfig<?> config, Enum<?> en) {
88+
public SerializableString[] allEnumValuesFromToString(MapperConfig<?> config) {
6989
SerializableString[] strs;
7090
if (config.isEnabled(EnumFeature.WRITE_ENUMS_TO_LOWERCASE)) {
7191
if ((strs = _enumToStringsLC) == null) {
72-
_enumToStringsLC = strs = _fetch(config, Enum::toString, true);
92+
_enumToStringsLC = strs = _fetch(config,
93+
e -> _toStringWithStrategy(config, e),
94+
true);
7395
}
7496
} else {
7597
if ((strs = _enumToStrings) == null) {
76-
_enumToStrings = strs = _fetch(config, Enum::toString, false);
98+
_enumToStrings = strs = _fetch(config,
99+
e -> _toStringWithStrategy(config, e),
100+
false);
77101
}
78102
}
79-
return strs[en.ordinal()];
103+
return strs;
80104
}
81105

82106
private String _nameWithStrategy(MapperConfig<?> config, Enum<?> en) {
@@ -87,6 +111,14 @@ private String _nameWithStrategy(MapperConfig<?> config, Enum<?> en) {
87111
return str;
88112
}
89113

114+
private String _toStringWithStrategy(MapperConfig<?> config, Enum<?> en) {
115+
String str = en.toString();
116+
if (_enumNamingStrategy != null) {
117+
str = _enumNamingStrategy.convertEnumToExternalName(config, _annotatedClass, str);
118+
}
119+
return str;
120+
}
121+
90122
private SerializableString[] _fetch(MapperConfig<?> config,
91123
Function<Enum<?>,String> accessor, boolean lowerCase) {
92124
final int len = _enumConstants.length;

src/test/java/tools/jackson/databind/jsonschema/NewSchemaTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ public void testBasicTraversal() throws Exception
228228
@Test
229229
public void testSimpleEnum() throws Exception
230230
{
231-
final Set<String> values = new TreeSet<String>();
231+
final Set<String> values = new TreeSet<>();
232232
ObjectWriter w = MAPPER.writer(EnumFeature.WRITE_ENUMS_USING_TO_STRING);
233233

234234
w.acceptJsonFormatVisitor(TestEnum.class, new JsonFormatVisitorWrapper.Base() {
@@ -247,7 +247,7 @@ public void format(JsonValueFormat format) { }
247247
});
248248

249249
assertEquals(3, values.size());
250-
TreeSet<String> exp = new TreeSet<String>(Arrays.asList(
250+
TreeSet<String> exp = new TreeSet<>(Arrays.asList(
251251
"ToString:A",
252252
"ToString:B",
253253
"ToString:C"

0 commit comments

Comments
 (0)