Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 39 additions & 65 deletions src/main/java/tools/jackson/databind/ser/jdk/EnumSerializer.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tools.jackson.databind.ser.jdk;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

Expand All @@ -11,12 +12,13 @@
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JacksonStdImpl;
import tools.jackson.databind.cfg.EnumFeature;
import tools.jackson.databind.introspect.AnnotatedClass;
import tools.jackson.databind.introspect.EnumNamingStrategyFactory;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import tools.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
import tools.jackson.databind.ser.std.StdScalarSerializer;
import tools.jackson.databind.util.EnumDefinition;
import tools.jackson.databind.util.EnumValues;
import tools.jackson.databind.util.EnumValuesToWrite;

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

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

/**
* Map with key as converted property class defined implementation of {@link EnumNamingStrategy}
* and with value as Enum names collected using <code>Enum.name()</code>.
*/
protected final EnumValues _valuesByEnumNaming;

/**
* Map that contains pre-resolved values for {@link Enum#toString} to use for serialization,
* while respecting {@link com.fasterxml.jackson.annotation.JsonProperty}
* and {@link tools.jackson.databind.cfg.EnumFeature#WRITE_ENUMS_TO_LOWERCASE}.
*/
protected final EnumValues _valuesByToString;

/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/

public EnumSerializer(EnumValues v, Boolean serializeAsIndex, EnumValues valuesByEnumNaming,
EnumValues valuesByToString)
public EnumSerializer(EnumValuesToWrite enumValuesToWrite, Boolean serializeAsIndex)
{
super(v.getEnumClass(), false);
_values = v;
super(enumValuesToWrite.enumClass(), false);
_enumValuesToWrite = enumValuesToWrite;
_serializeAsIndex = serializeAsIndex;
_valuesByEnumNaming = valuesByEnumNaming;
_valuesByToString = valuesByToString;
}

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

/**
Expand All @@ -103,8 +88,7 @@ public ValueSerializer<?> createContextual(SerializationContext ctxt,
Boolean serializeAsIndex = _isShapeWrittenUsingIndex(type,
format, false, _serializeAsIndex);
if (!Objects.equals(serializeAsIndex, _serializeAsIndex)) {
return new EnumSerializer(_values, serializeAsIndex,
_valuesByEnumNaming, _valuesByToString);
return new EnumSerializer(_enumValuesToWrite, serializeAsIndex);
}
}
return this;
Expand All @@ -116,7 +100,12 @@ public ValueSerializer<?> createContextual(SerializationContext ctxt,
/**********************************************************************
*/

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

/*
/**********************************************************************
Expand All @@ -128,21 +117,17 @@ public ValueSerializer<?> createContextual(SerializationContext ctxt,
public final void serialize(Enum<?> en, JsonGenerator g, SerializationContext ctxt)
throws JacksonException
{
if (_valuesByEnumNaming != null) {
g.writeString(_valuesByEnumNaming.serializedValueFor(en));
return;
}
// Serialize as index?
if (_serializeAsIndex(ctxt)) {
g.writeNumber(en.ordinal());
return;
}
// [databind#749]: or via toString()?
final MapperConfig<?> config = ctxt.getConfig();
if (ctxt.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)) {
g.writeString(_valuesByToString.serializedValueFor(en));
g.writeString(_enumValuesToWrite.enumValueFromToString(config, en));
return;
}
g.writeString(_values.serializedValueFor(en));
}
g.writeString(_enumValuesToWrite.enumValueFromName(config, en));
}

/*
Expand All @@ -154,28 +139,30 @@ public final void serialize(Enum<?> en, JsonGenerator g, SerializationContext ct
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
{
SerializationContext serializers = visitor.getContext();
if (_serializeAsIndex(serializers)) {
SerializationContext ctxt = visitor.getContext();
if (_serializeAsIndex(ctxt)) {
visitIntFormat(visitor, typeHint, JsonParser.NumberType.INT);
return;
}
JsonStringFormatVisitor stringVisitor = visitor.expectStringFormat(typeHint);
if (stringVisitor != null) {
Set<String> enums = new LinkedHashSet<String>();
Set<String> enumStrings = new LinkedHashSet<>();

// Use toString()?
if ((serializers != null) &&
serializers.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)) {
for (SerializableString value : _valuesByToString.values()) {
enums.add(value.getValue());
List<Enum<?>> enums = _enumValuesToWrite.enums();
if (_serializeAsIndex(ctxt)) {
for (Enum<?> en : enums) {
enumStrings.add(String.valueOf(en.ordinal()));
}
} else {
// No, serialize using name() or explicit overrides
for (SerializableString value : _values.values()) {
enums.add(value.getValue());
final MapperConfig<?> config = ctxt.getConfig();
SerializableString[] values = ctxt.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)
? _enumValuesToWrite.allEnumValuesFromToString(config)
: _enumValuesToWrite.allEnumValuesFromName(config);
for (SerializableString sstr : values) {
enumStrings.add(sstr.getValue());
}
}
stringVisitor.enumTypes(enums);
stringVisitor.enumTypes(enumStrings);
}
}

Expand Down Expand Up @@ -222,17 +209,4 @@ protected static Boolean _isShapeWrittenUsingIndex(Class<?> enumClass,
"Unsupported serialization shape (%s) for Enum %s, not supported as %s annotation",
shape, enumClass.getName(), (fromClass? "class" : "property")));
}

/**
* Factory method used to resolve an instance of {@link EnumValues}
* with {@link EnumNamingStrategy} applied for the target class.
*/
protected static EnumValues constructEnumNamingStrategyValues(SerializationConfig config, Class<Enum<?>> enumClass,
AnnotatedClass annotatedClass) {
Object namingDef = config.getAnnotationIntrospector().findEnumNamingStrategy(config, annotatedClass);
EnumNamingStrategy enumNamingStrategy = EnumNamingStrategyFactory.createEnumNamingStrategyInstance(
namingDef, config.canOverrideAccessModifiers(), config.getEnumNamingStrategy());
return enumNamingStrategy == null ? null : EnumValues.constructUsingEnumNamingStrategy(
config, annotatedClass, enumNamingStrategy);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,11 @@ public void serialize(Object value, JsonGenerator g, SerializationContext ctxt)
// 26-Nov-2025, tatu: In 3.0 order was opposite (TO_STRING first,
// then INDEX); changed in 3.1
if (ctxt.isEnabled(EnumFeature.WRITE_ENUM_KEYS_USING_INDEX)) {
// 14-Sep-2019, tatu: [databind#2129] Use this specific feature
g.writeName(String.valueOf(en.ordinal()));
} else if (ctxt.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)) {
g.writeName(_valuesToWrite.fromToString(ctxt.getConfig(), en));
g.writeName(_valuesToWrite.enumValueFromToString(ctxt.getConfig(), en));
} else {
g.writeName(_valuesToWrite.fromName(ctxt.getConfig(), en));
g.writeName(_valuesToWrite.enumValueFromName(ctxt.getConfig(), en));
}
}
}
Expand Down
13 changes: 4 additions & 9 deletions src/main/java/tools/jackson/databind/util/EnumValues.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@

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

/**
* NOTE: do NOT call this if configuration may change, and choice between toString()
* and name() might change dynamically.
*
* @deprecated Since 3.1 call {@link #constructFromName} or {@link #constructFromToString}
* instead.
*/
@Deprecated // since 3.1
public static EnumValues construct(SerializationConfig config, AnnotatedClass enumClass) {
if (config.isEnabled(EnumFeature.WRITE_ENUMS_USING_TO_STRING)) {
return constructFromToString(config, enumClass);
Expand Down
44 changes: 38 additions & 6 deletions src/main/java/tools/jackson/databind/util/EnumValuesToWrite.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package tools.jackson.databind.util;

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

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

public SerializableString fromName(MapperConfig<?> config, Enum<?> en) {
@SuppressWarnings("unchecked")
public Class<Enum<?>> enumClass() {
Class<?> cls = _annotatedClass.getRawType();
return (Class<Enum<?>>) cls;
}

public List<Enum<?>> enums() {
return Arrays.asList(_enumConstants);
}

public SerializableString enumValueFromName(MapperConfig<?> config, Enum<?> en) {
return allEnumValuesFromName(config)[en.ordinal()];
}

public SerializableString[] allEnumValuesFromName(MapperConfig<?> config) {
SerializableString[] strs;
if (config.isEnabled(EnumFeature.WRITE_ENUMS_TO_LOWERCASE)) {
if ((strs = _enumNamesLC) == null) {
Expand All @@ -62,21 +78,29 @@ public SerializableString fromName(MapperConfig<?> config, Enum<?> en) {
false);
}
}
return strs[en.ordinal()];
return strs;
}

public SerializableString enumValueFromToString(MapperConfig<?> config, Enum<?> en) {
return allEnumValuesFromToString(config)[en.ordinal()];
}

public SerializableString fromToString(MapperConfig<?> config, Enum<?> en) {
public SerializableString[] allEnumValuesFromToString(MapperConfig<?> config) {
SerializableString[] strs;
if (config.isEnabled(EnumFeature.WRITE_ENUMS_TO_LOWERCASE)) {
if ((strs = _enumToStringsLC) == null) {
_enumToStringsLC = strs = _fetch(config, Enum::toString, true);
_enumToStringsLC = strs = _fetch(config,
e -> _toStringWithStrategy(config, e),
true);
}
} else {
if ((strs = _enumToStrings) == null) {
_enumToStrings = strs = _fetch(config, Enum::toString, false);
_enumToStrings = strs = _fetch(config,
e -> _toStringWithStrategy(config, e),
false);
}
}
return strs[en.ordinal()];
return strs;
}

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

private String _toStringWithStrategy(MapperConfig<?> config, Enum<?> en) {
String str = en.toString();
if (_enumNamingStrategy != null) {
str = _enumNamingStrategy.convertEnumToExternalName(config, _annotatedClass, str);
}
return str;
}

private SerializableString[] _fetch(MapperConfig<?> config,
Function<Enum<?>,String> accessor, boolean lowerCase) {
final int len = _enumConstants.length;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public void testBasicTraversal() throws Exception
@Test
public void testSimpleEnum() throws Exception
{
final Set<String> values = new TreeSet<String>();
final Set<String> values = new TreeSet<>();
ObjectWriter w = MAPPER.writer(EnumFeature.WRITE_ENUMS_USING_TO_STRING);

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

assertEquals(3, values.size());
TreeSet<String> exp = new TreeSet<String>(Arrays.asList(
TreeSet<String> exp = new TreeSet<>(Arrays.asList(
"ToString:A",
"ToString:B",
"ToString:C"
Expand Down
Loading