Skip to content

Move Enum-related DeserializationFeatures into EnumFeature #5088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 11, 2025
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
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Versions: 3.x (for earlier see VERSION-2.x)
- Branch rename "master" -> "3.x" [JSTEP-12]
#5080: Move Enum-related SerializationFeatures into EnumFeature (3.0)
(contributed by Joo-Hyuk K)
#5079: Move Enum-related DeserializationFeatures into EnumFeature (3.0)
(contributed by Joo-Hyuk K)

3.0.0-rc3 (not yet released)

Expand Down
56 changes: 0 additions & 56 deletions src/main/java/tools/jackson/databind/DeserializationFeature.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,6 @@ public enum DeserializationFeature implements ConfigFeature
*/
FAIL_ON_NULL_FOR_PRIMITIVES(true),

/**
* Feature that determines whether JSON integer numbers are valid
* values to be used for deserializing Java enum values.
* If set to 'false' numbers are acceptable and are used to map to
* ordinal() of matching enumeration value; if 'true', numbers are
* not allowed and a {@link DatabindException} will be thrown.
* Latter behavior makes sense if there is concern that accidental
* mapping from integer values to enums might happen (and when enums
* are always serialized as JSON Strings)
*<p>
* Feature is disabled by default.
*/
FAIL_ON_NUMBERS_FOR_ENUMS(false),

/**
* Feature that determines what happens when type of a polymorphic
* value (indicated for example by {@link com.fasterxml.jackson.annotation.JsonTypeInfo})
Expand Down Expand Up @@ -407,48 +393,6 @@ public enum DeserializationFeature implements ConfigFeature
*/
ACCEPT_FLOAT_AS_INT(true),

/**
* Feature that determines the deserialization mechanism used for
* Enum values: if enabled, Enums are assumed to have been serialized using
* return value of {@code Enum.toString()};
* if disabled, return value of {@code Enum.name()} is assumed to have been used.
*<p>
* Note: this feature should usually have same value
* as {@link SerializationFeature#WRITE_ENUMS_USING_TO_STRING}.
*<p>
* Feature is enabled by default as of Jackson 3.0 (in 2.x it was disabled).
*/
READ_ENUMS_USING_TO_STRING(true),

/**
* Feature that allows unknown Enum values to be parsed as {@code null} values.
* If disabled, unknown Enum values will throw exceptions.
* <p>
* Note that in some cases this will effectively ignore unknown {@code Enum} values,
* e.g. when the unknown values are used as keys of {@link java.util.EnumMap}
* or values of {@link java.util.EnumSet}: this is because these data structures cannot
* store {@code null} values.
* <p>
* Also note that this feature has lower precedence than
* {@link DeserializationFeature#READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE},
* meaning this feature will work only if latter feature is disabled.
* <p>
* Feature is disabled by default.
*/
READ_UNKNOWN_ENUM_VALUES_AS_NULL(false),

/**
* Feature that allows unknown Enum values to be ignored and replaced by a predefined value specified through
* {@link com.fasterxml.jackson.annotation.JsonEnumDefaultValue @JsonEnumDefaultValue} annotation.
* If disabled, unknown Enum values will throw exceptions.
* If enabled, but no predefined default Enum value is specified, an exception will be thrown as well.
* <p>
* Note that this feature has higher precedence than {@link DeserializationFeature#READ_UNKNOWN_ENUM_VALUES_AS_NULL}.
* <p>
* Feature is disabled by default.
*/
READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE(false),

/*
/**********************************************************************
/* Other
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/tools/jackson/databind/ObjectMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2141,6 +2141,17 @@ public ObjectReader reader(DeserializationFeature first,
return _newReader(deserializationConfig().with(first, other));
}

/**
* Factory method for constructing {@link ObjectReader} with
* specified feature enabled (compared to settings that this
* mapper instance has).
* Note that the resulting instance is NOT usable as is,
* without defining expected value type.
*/
public ObjectReader reader(DatatypeFeature feature) {
return _newReader(deserializationConfig().with(feature));
}

/**
* Factory method for constructing {@link ObjectReader} that will
* update given Object (usually Bean, but can be a Collection or Map
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public CoercionAction findCoercion(DeserializationConfig config,
break;
case Integer:
if (targetType == LogicalType.Enum) {
if (config.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
if (config.isEnabled(EnumFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
return CoercionAction.Fail;
}
}
Expand Down
85 changes: 83 additions & 2 deletions src/main/java/tools/jackson/databind/cfg/EnumFeature.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package tools.jackson.databind.cfg;

import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.DatabindException;

/**
* New Datatype-specific configuration options related to handling of
* {@link java.lang.Enum} types.
*/
public enum EnumFeature implements DatatypeFeature
{
/*
/**********************************************************************
/* READ
/**********************************************************************
*/

/**
* Feature that determines standard deserialization mechanism used for
* Enum values: if enabled, Enums are assumed to have been serialized using
Expand All @@ -22,6 +28,81 @@ public enum EnumFeature implements DatatypeFeature
*/
READ_ENUM_KEYS_USING_INDEX(false),

/**
* Feature that determines whether JSON integer numbers are valid
* values to be used for deserializing Java enum values.
* If set to 'false' numbers are acceptable and are used to map to
* ordinal() of matching enumeration value; if 'true', numbers are
* not allowed and a {@link DatabindException} will be thrown.
* Latter behavior makes sense if there is concern that accidental
* mapping from integer values to enums might happen (and when enums
* are always serialized as JSON Strings)
*<p>
* Feature used to be one of {@link tools.jackson.databind.DeserializationFeature}s
* in Jackson 2.x but was moved here in 3.0.
*<p>
* Feature is disabled by default.
*/
FAIL_ON_NUMBERS_FOR_ENUMS(false),

/**
* Feature that determines the deserialization mechanism used for
* Enum values: if enabled, Enums are assumed to have been serialized using
* return value of {@code Enum.toString()};
* if disabled, return value of {@code Enum.name()} is assumed to have been used.
*<p>
* Note: this feature should usually have same value
* as {@link #WRITE_ENUMS_USING_TO_STRING}.
*<p>
* Feature used to be one of {@link tools.jackson.databind.DeserializationFeature}s
* in Jackson 2.x but was moved here in 3.0.
*<p>
* Feature is enabled by default as of Jackson 3.0 (in 2.x it was disabled).
*/
READ_ENUMS_USING_TO_STRING(true),

/**
* Feature that allows unknown Enum values to be parsed as {@code null} values.
* If disabled, unknown Enum values will throw exceptions.
* <p>
* Note that in some cases this will effectively ignore unknown {@code Enum} values,
* e.g. when the unknown values are used as keys of {@link java.util.EnumMap}
* or values of {@link java.util.EnumSet}: this is because these data structures cannot
* store {@code null} values.
* <p>
* Also note that this feature has lower precedence than
* {@link #READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE},
* meaning this feature will work only if latter feature is disabled.
*<p>
* Feature used to be one of {@link tools.jackson.databind.DeserializationFeature}s
* in Jackson 2.x but was moved here in 3.0.
*<p>
* Feature is disabled by default.
*/
READ_UNKNOWN_ENUM_VALUES_AS_NULL(false),

/**
* Feature that allows unknown Enum values to be ignored and replaced by a predefined value specified through
* {@link com.fasterxml.jackson.annotation.JsonEnumDefaultValue @JsonEnumDefaultValue} annotation.
* If disabled, unknown Enum values will throw exceptions.
* If enabled, but no predefined default Enum value is specified, an exception will be thrown as well.
* <p>
* Note that this feature has higher precedence than {@link #READ_UNKNOWN_ENUM_VALUES_AS_NULL}.
*<p>
* Feature used to be one of {@link tools.jackson.databind.DeserializationFeature}s
* in Jackson 2.x but was moved here in 3.0.
*<p>
* Feature is disabled by default.
*/
READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE(false),

/*
/**********************************************************************
/* WRITE
/**********************************************************************
*/


/**
* Feature that determines standard serialization mechanism used for
* Enum values: if enabled, return value of <code>Enum.name().toLowerCase()</code>
Expand All @@ -43,7 +124,7 @@ public enum EnumFeature implements DatatypeFeature
* is used; if disabled, return value of <code>Enum.name()</code> is used.
*<p>
* Note: this feature should usually have same value
* as {@link DeserializationFeature#READ_ENUMS_USING_TO_STRING}.
* as {@link #READ_ENUMS_USING_TO_STRING}.
*<p>
* Feature used to be one of {@link tools.jackson.databind.SerializationFeature}s
* in Jackson 2.x but was moved here in 3.0.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ public B configureForJackson2() {
.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES,
DeserializationFeature.FAIL_ON_TRAILING_TOKENS)
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
.disable(EnumFeature.READ_ENUMS_USING_TO_STRING)
.enable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.enable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS,
DateTimeFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import tools.jackson.core.*;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JacksonStdImpl;
import tools.jackson.databind.cfg.CoercionAction;
import tools.jackson.databind.cfg.CoercionInputShape;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.cfg.*;
import tools.jackson.databind.deser.SettableBeanProperty;
import tools.jackson.databind.deser.ValueInstantiator;
import tools.jackson.databind.deser.std.StdScalarDeserializer;
Expand Down Expand Up @@ -222,7 +220,7 @@ private CompactStringObjectMap _resolveCurrentLookup(DeserializationContext ctxt
if (_lookupByEnumNaming != null) {
return _lookupByEnumNaming;
}
return ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
return ctxt.isEnabled(EnumFeature.READ_ENUMS_USING_TO_STRING)
? _lookupByToString
: _lookupByName;
}
Expand All @@ -236,9 +234,9 @@ protected Object _fromInteger(JsonParser p, DeserializationContext ctxt,

// First, check legacy setting for slightly different message
if (act == CoercionAction.Fail) {
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
if (ctxt.isEnabled(EnumFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
return ctxt.handleWeirdNumberValue(_enumClass(), index,
"not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"
"not allowed to deserialize Enum value out of number: disable DeserializationConfig.EnumFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"
);
}
// otherwise this will force failure with new setting
Expand Down Expand Up @@ -318,7 +316,7 @@ private final Object _deserializeAltString(JsonParser p, DeserializationContext
return match;
}
}
if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
if (!ctxt.isEnabled(EnumFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
&& !_isFromIntValue) {
// [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
char c = name.charAt(0);
Expand Down Expand Up @@ -375,7 +373,7 @@ protected boolean useNullForUnknownEnum(DeserializationContext ctxt) {
if (_useNullForUnknownEnum != null) {
return _useNullForUnknownEnum;
}
return ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
return ctxt.isEnabled(EnumFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
}

// @since 2.15
Expand All @@ -387,7 +385,7 @@ protected boolean useDefaultValueForUnknownEnum(DeserializationContext ctxt) {
return _useDefaultValueForUnknownEnum;
}
// Otherwise, check the global setting
return ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
return ctxt.isEnabled(EnumFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
}
// No default value? then false
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import tools.jackson.core.*;
import tools.jackson.databind.*;
import tools.jackson.databind.cfg.EnumFeature;
import tools.jackson.databind.deser.*;
import tools.jackson.databind.deser.bean.PropertyBasedCreator;
import tools.jackson.databind.deser.bean.PropertyValueBuffer;
Expand Down Expand Up @@ -267,7 +268,7 @@ public EnumMap<?,?> deserialize(JsonParser p, DeserializationContext ctxt,
Enum<?> key = (Enum<?>) _keyDeserializer.deserializeKey(keyStr, ctxt);
JsonToken t = p.nextToken();
if (key == null) {
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
if (!ctxt.isEnabled(EnumFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
return (EnumMap<?,?>) ctxt.handleWeirdStringValue(_enumClass, keyStr,
"value not one of declared Enum instance names for %s",
_containerType.getKeyType());
Expand Down Expand Up @@ -360,7 +361,7 @@ public EnumMap<?,?> _deserializeUsingProperties(JsonParser p, DeserializationCon
// but we need to let key deserializer handle it separately, nonetheless
Enum<?> key = (Enum<?>) _keyDeserializer.deserializeKey(keyName, ctxt);
if (key == null) {
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
if (!ctxt.isEnabled(EnumFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
return (EnumMap<?,?>) ctxt.handleWeirdStringValue(_enumClass, keyName,
"value not one of declared Enum instance names for %s",
_containerType.getKeyType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import tools.jackson.core.JsonToken;

import tools.jackson.databind.*;
import tools.jackson.databind.cfg.EnumFeature;
import tools.jackson.databind.deser.SettableBeanProperty;
import tools.jackson.databind.deser.ValueInstantiator;
import tools.jackson.databind.deser.bean.PropertyBasedCreator;
Expand Down Expand Up @@ -194,14 +195,14 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt)
Throwable t = ClassUtil.throwRootCauseIfJacksonE(e);
if (t instanceof IllegalArgumentException) {
// [databind#4979]: unknown as default
if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
if (ctxt.isEnabled(EnumFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
// ... only if we DO have a default
if (_defaultValue != null) {
return _defaultValue;
}
}
// [databind#1642]: unknown as null
if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
if (ctxt.isEnabled(EnumFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
return null;
}
// 12-Oct-2021, tatu: Should probably try to provide better exception since
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public Object deserializeKey(String key, DeserializationContext ctxt)
ClassUtil.exceptionMessage(re));
}
if (ClassUtil.isEnumType(_keyClass)
&& ctxt.getConfig().isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
&& ctxt.getConfig().isEnabled(EnumFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
return null;
}
return ctxt.handleWeirdKey(_keyClass, key, "not a valid representation");
Expand Down Expand Up @@ -418,9 +418,9 @@ public Object _parse(String key, DeserializationContext ctxt)
}
if (e == null) {
if ((_enumDefaultValue != null)
&& ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
&& ctxt.isEnabled(EnumFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
e = _enumDefaultValue;
} else if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
} else if (!ctxt.isEnabled(EnumFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
return ctxt.handleWeirdKey(_keyClass, key, "not one of the values accepted for Enum class: %s",
res.getEnumIds());
}
Expand All @@ -436,7 +436,7 @@ protected EnumResolver _resolveCurrentResolver(DeserializationContext ctxt) {
if (_byEnumNamingResolver != null) {
return _byEnumNamingResolver;
}
return ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
return ctxt.isEnabled(EnumFeature.READ_ENUMS_USING_TO_STRING)
? _byToStringResolver
: _byNameResolver;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;

import tools.jackson.databind.*;
import tools.jackson.databind.cfg.CoercionAction;
import tools.jackson.databind.cfg.CoercionInputShape;
import tools.jackson.databind.cfg.*;
import tools.jackson.databind.exc.MismatchedInputException;
import tools.jackson.databind.type.LogicalType;

Expand Down Expand Up @@ -43,7 +42,7 @@ protected enum EnumCoerce {
public void testLegacyDefaults() throws Exception
{
// first, verify default settings which do not accept empty String:
assertFalse(MAPPER.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS));
assertFalse(MAPPER.isEnabled(EnumFeature.FAIL_ON_NUMBERS_FOR_ENUMS));
}

@Test
Expand Down Expand Up @@ -98,7 +97,7 @@ public void testEnumFromIntFailLegacy() throws Exception
assertEquals(EnumCoerce.values()[1], result);

try {
r.with(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
r.with(EnumFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
.readValue("1");
fail("Should not pass");
} catch (Exception e) {
Expand Down
Loading