Skip to content

Global enum naming strategy to use when it's not set directly on a class level #4716

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

Closed
wants to merge 7 commits into from
Closed
4 changes: 4 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ Project: jackson-databind
#4709: Add `JacksonCollectors` with `toArrayNode()` implementation
(contributed by @rikkarth)

2.17.3 (not yet released)

#4718: Should not fail on trying to serialize `java.time.DateTimeException`

2.17.2 (05-Jul-2024)

#4561: Issues using jackson-databind 2.17.1 with Reactor
Expand Down
81 changes: 61 additions & 20 deletions src/main/java/tools/jackson/databind/cfg/BaseSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public final class BaseSettings
*/
protected final PropertyNamingStrategy _propertyNamingStrategy;

/**
* Custom enum naming strategy in use, if any.
*/
protected final EnumNamingStrategy _enumNamingStrategy;

/**
* Provider for creating {@link AccessorNamingStrategy} instances to use
*/
Expand Down Expand Up @@ -136,16 +141,20 @@ public final class BaseSettings
/**********************************************************************
*/

public BaseSettings(AnnotationIntrospector ai,
PropertyNamingStrategy pns, AccessorNamingStrategy.Provider accNaming,
TypeResolverBuilder<?> defaultTyper, PolymorphicTypeValidator ptv,
DateFormat dateFormat, HandlerInstantiator hi,
Locale locale, TimeZone tz, Base64Variant defaultBase64,
CacheProvider cacheProvider, JsonNodeFactory nodeFactory,
ConstructorDetector ctorDetector)
/**
* @since 2.19
*/
public BaseSettings(AnnotationIntrospector ai, PropertyNamingStrategy pns,
EnumNamingStrategy ens, AccessorNamingStrategy.Provider accNaming,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't make source incompatible changes. Add a new constructor and deprecate the old one.

TypeResolverBuilder<?> defaultTyper, PolymorphicTypeValidator ptv,
DateFormat dateFormat, HandlerInstantiator hi,
Locale locale, TimeZone tz, Base64Variant defaultBase64,
CacheProvider cacheProvider, JsonNodeFactory nodeFactory,
ConstructorDetector ctorDetector)
{
_annotationIntrospector = ai;
_propertyNamingStrategy = pns;
_enumNamingStrategy = ens;
_accessorNaming = accNaming;
_defaultTyper = defaultTyper;
_typeValidator = ptv;
Expand All @@ -159,6 +168,22 @@ public BaseSettings(AnnotationIntrospector ai,
_ctorDetector = ctorDetector;
}

/**
* @deprecated Since 2.19, use variant that takes {@link EnumNamingStrategy} instead.
*/
@Deprecated
public BaseSettings(AnnotationIntrospector ai,
PropertyNamingStrategy pns, AccessorNamingStrategy.Provider accNaming,
TypeResolverBuilder<?> defaultTyper, PolymorphicTypeValidator ptv,
DateFormat dateFormat, HandlerInstantiator hi,
Locale locale, TimeZone tz, Base64Variant defaultBase64,
CacheProvider cacheProvider, JsonNodeFactory nodeFactory,
ConstructorDetector ctorDetector)
{
this(ai, pns, null, accNaming, defaultTyper, ptv, dateFormat, hi, locale, tz,
defaultBase64, cacheProvider, nodeFactory, ctorDetector);
}

/*
/**********************************************************************
/* Factory methods
Expand All @@ -169,7 +194,7 @@ public BaseSettings withAnnotationIntrospector(AnnotationIntrospector ai) {
if (_annotationIntrospector == ai) {
return this;
}
return new BaseSettings(ai, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(ai, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -186,7 +211,19 @@ public BaseSettings with(PropertyNamingStrategy pns) {
if (_propertyNamingStrategy == pns) {
return this;
}
return new BaseSettings(_annotationIntrospector, pns, _accessorNaming,
return new BaseSettings(_annotationIntrospector, pns, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}

/**
* @since 2.19
*/
public BaseSettings with(EnumNamingStrategy ens) {
if (_propertyNamingStrategy == ens) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, ens, _accessorNaming,
_defaultTyper, _typeValidator, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -195,7 +232,7 @@ public BaseSettings with(AccessorNamingStrategy.Provider p) {
if (_accessorNaming == p) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, p,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, p,
_defaultTyper, _typeValidator, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -204,7 +241,7 @@ public BaseSettings with(TypeResolverBuilder<?> typer) {
if (_defaultTyper == typer) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
typer, _typeValidator, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -213,7 +250,7 @@ public BaseSettings with(PolymorphicTypeValidator ptv) {
if (_typeValidator == ptv) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, ptv, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -227,7 +264,7 @@ public BaseSettings with(DateFormat df) {
if ((df != null) && hasExplicitTimeZone()) {
df = _force(df, _timeZone);
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, df, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -236,7 +273,7 @@ public BaseSettings with(HandlerInstantiator hi) {
if (_handlerInstantiator == hi) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, _dateFormat, hi, _locale,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -245,7 +282,7 @@ public BaseSettings with(Locale l) {
if (_locale == l) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, _dateFormat, _handlerInstantiator, l,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -261,7 +298,7 @@ public BaseSettings with(TimeZone tz)
return this;
}
DateFormat df = _force(_dateFormat, (tz == null) ? DEFAULT_TIMEZONE : tz);
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, df, _handlerInstantiator, _locale,
tz, _defaultBase64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -270,7 +307,7 @@ public BaseSettings with(Base64Variant base64) {
if (base64 == _defaultBase64) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, _dateFormat, _handlerInstantiator, _locale,
_timeZone, base64, _cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -284,7 +321,7 @@ public BaseSettings with(CacheProvider cacheProvider) {
if (cacheProvider == _cacheProvider) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, cacheProvider, _nodeFactory, _ctorDetector);
}
Expand All @@ -293,7 +330,7 @@ public BaseSettings with(JsonNodeFactory nodeFactory) {
if (nodeFactory == _nodeFactory) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _cacheProvider, nodeFactory, _ctorDetector);
}
Expand All @@ -302,7 +339,7 @@ public BaseSettings with(ConstructorDetector ctorDetector) {
if (ctorDetector == _ctorDetector) {
return this;
}
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _accessorNaming,
return new BaseSettings(_annotationIntrospector, _propertyNamingStrategy, _enumNamingStrategy, _accessorNaming,
_defaultTyper, _typeValidator, _dateFormat, _handlerInstantiator, _locale,
_timeZone, _defaultBase64, _cacheProvider, _nodeFactory, ctorDetector);
}
Expand All @@ -321,6 +358,10 @@ public PropertyNamingStrategy getPropertyNamingStrategy() {
return _propertyNamingStrategy;
}

public EnumNamingStrategy getEnumNamingStrategy() {
return _enumNamingStrategy;
}

public AccessorNamingStrategy.Provider getAccessorNaming() {
return _accessorNaming;
}
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/tools/jackson/databind/cfg/MapperBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public abstract class MapperBuilder<M extends ObjectMapper,

protected final static BaseSettings DEFAULT_BASE_SETTINGS = new BaseSettings(
DEFAULT_ANNOTATION_INTROSPECTOR,
null, DEFAULT_ACCESSOR_NAMING,
null, null, DEFAULT_ACCESSOR_NAMING,
null, // no default typing, by default
DEFAULT_TYPE_VALIDATOR, // and polymorphic type by class won't pass either
StdDateFormat.instance, null,
Expand Down Expand Up @@ -1236,6 +1236,21 @@ public B propertyNamingStrategy(PropertyNamingStrategy s) {
return _this();
}

/**
* Method for configuring {@link EnumNamingStrategy} to use for adapting
* POJO enum names (internal) into content property names (external)
*
* @param s Strategy instance to use
*
* @return Builder instance itself to allow chaining
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add @since 2.18 to all new public methods (including the ones that you didn't add javadoc to)

*
* @since 2.19
*/
public B enumNamingStrategy(EnumNamingStrategy s) {
_baseSettings = _baseSettings.with(s);
return _this();
}

/**
* Method for configuring {@link AccessorNamingStrategy} to use for auto-detecting
* accessor ("getter") and mutator ("setter") methods based on naming of methods.
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/tools/jackson/databind/cfg/MapperConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,13 @@ public final PropertyNamingStrategy getPropertyNamingStrategy() {
return _base.getPropertyNamingStrategy();
}

/**
* @since 2.19
*/
public final EnumNamingStrategy getEnumNamingStrategy() {
return _base.getEnumNamingStrategy();
}

// @since 2.12
public final AccessorNamingStrategy.Provider getAccessorNaming() {
return _base.getAccessorNaming();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1743,7 +1743,7 @@ protected EnumResolver constructEnumNamingStrategyResolver(DeserializationConfig
{
Object namingDef = config.getAnnotationIntrospector().findEnumNamingStrategy(config, enumClass);
EnumNamingStrategy enumNamingStrategy = EnumNamingStrategyFactory.createEnumNamingStrategyInstance(
namingDef, config.canOverrideAccessModifiers());
namingDef, config.canOverrideAccessModifiers(), config.getEnumNamingStrategy());
return enumNamingStrategy == null ? null
: EnumResolver.constructUsingEnumNamingStrategy(config, enumClass, enumNamingStrategy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,36 @@ public class EnumNamingStrategyFactory
*
* @param namingDef subclass of {@link EnumNamingStrategy} to initialize an instance of.
* @param canOverrideAccessModifiers whether to override access modifiers when instantiating the naming strategy.
*
* @return an instance of {@link EnumNamingStrategy} if {@code namingDef} is a subclass of {@link EnumNamingStrategy},
* {@code null} if {@code namingDef} is {@code null},
* and an instance of {@link EnumNamingStrategy} if {@code namingDef} already is one.
*
* @throws IllegalArgumentException if {@code namingDef} is not an instance of {@link java.lang.Class} or
* not a subclass of {@link EnumNamingStrategy}.
*
* @deprecated Since 2.19, use variant that takes {@link EnumNamingStrategy} instead.
*/
public static EnumNamingStrategy createEnumNamingStrategyInstance(Object namingDef, boolean canOverrideAccessModifiers) {
return createEnumNamingStrategyInstance(namingDef, canOverrideAccessModifiers, null);
}

/**
* Factory method for creating an instance of {@link EnumNamingStrategy} from a provided {@code namingDef}.
*
* @param namingDef subclass of {@link EnumNamingStrategy} to initialize an instance of.
* @param canOverrideAccessModifiers whether to override access modifiers when instantiating the naming strategy.
* @param defaultNamingStrategy configured global {@link EnumNamingStrategy} to use in case {@code namingDef} is not provided.
* @return an instance of {@link EnumNamingStrategy} if {@code namingDef} is a subclass of {@link EnumNamingStrategy},
* {@code null} if {@code namingDef} is {@code null},
* and an instance of {@link EnumNamingStrategy} if {@code namingDef} already is one.
* @throws IllegalArgumentException if {@code namingDef} is not an instance of {@link java.lang.Class} or
* not a subclass of {@link EnumNamingStrategy}.
*
* @since 2.19
*/
public static EnumNamingStrategy createEnumNamingStrategyInstance(Object namingDef, boolean canOverrideAccessModifiers,
EnumNamingStrategy defaultNamingStrategy) {
if (namingDef == null) {
return null;
return defaultNamingStrategy;
}
if (namingDef instanceof EnumNamingStrategy) {
return (EnumNamingStrategy) namingDef;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ protected static EnumValues constructEnumNamingStrategyValues(SerializationConfi
AnnotatedClass annotatedClass) {
Object namingDef = config.getAnnotationIntrospector().findEnumNamingStrategy(config, annotatedClass);
EnumNamingStrategy enumNamingStrategy = EnumNamingStrategyFactory.createEnumNamingStrategyInstance(
namingDef, config.canOverrideAccessModifiers());
namingDef, config.canOverrideAccessModifiers(), config.getEnumNamingStrategy());
return enumNamingStrategy == null ? null : EnumValues.constructUsingEnumNamingStrategy(
config, annotatedClass, enumNamingStrategy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,4 +291,17 @@ public void testEnumMixInDeserializationTest() throws Exception {
BaseEnum deser = mapper.readValue(q("realName"), BaseEnum.class);
assertEquals(BaseEnum.REAL_NAME, deser);
}

@Test
void testUseEnumMappingStrategySetInMapper() {
ObjectMapper mapper = jsonMapperBuilder()
.enumNamingStrategy(EnumNamingStrategies.CamelCaseStrategy.INSTANCE)
Copy link
Member

@JooHyukKim JooHyukKim Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems awkward.
How about .enumNamingStrategy(EnumNamingStrategies.CamelCaseStrategy.class) then handle the instance internally?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think strategy passed should be instance, not class. This is used for most configuration; mapper/builder not having to dynamically construct instances.
If we want instantiation, should instead pass factory; Class only to be used in cases where we must (from Annotation where you cannot indicate instance but only Class).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could just ask for new EnumNamingStrategies.CamelCaseStrategy(), but if there's already INSTANCE can use that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, makes sense👍🏼

.build();

BaseEnum result = mapper.readValue(q("realName"), BaseEnum.class);
assertEquals(BaseEnum.REAL_NAME, result);

String resultString = mapper.writeValueAsString(result);
assertEquals(q("realName"), resultString);
}
}