Skip to content
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

Handle Enum introspection of values and aliases via AnnotatedClass instead of Class<?> #3832

Merged
merged 55 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
0ff49d2
Add test cases
JooHyukKim Mar 18, 2023
d9b70a9
Allow construction of EnumResolver for mixin class
JooHyukKim Mar 18, 2023
b11db13
Remove Pojo example tests
JooHyukKim Mar 18, 2023
fbcc1c3
improve JavaDoc
JooHyukKim Mar 18, 2023
3ebe045
Clean up tests
JooHyukKim Mar 18, 2023
4cdef6c
Fix failing test
JooHyukKim Mar 18, 2023
bcd9025
Add test for mix-in of Wrapper field
JooHyukKim Mar 18, 2023
ed21cf3
Fix documentation
JooHyukKim Mar 18, 2023
e33619e
Implement JacksonAnnotationIntrospector
JooHyukKim Mar 23, 2023
39ff50d
Merge branch '2787-allow-mixins-for-enums' of https://github.com/JooH…
JooHyukKim Mar 23, 2023
51f923b
Merge branch '2787-allow-mixins-for-enums' of https://github.com/JooH…
JooHyukKim Mar 23, 2023
1f7bbc1
Remove mixin from EnumResolver
JooHyukKim Mar 23, 2023
9478871
Update BasicDeserializerFactory.java
JooHyukKim Mar 23, 2023
2a80825
Add MapperConfig as first parameter
JooHyukKim Mar 24, 2023
b9a0677
Filter self referencing properties with JsonFormat.Shape is `OBJECT`
JooHyukKim Mar 27, 2023
05d77bb
Add more description
JooHyukKim Mar 27, 2023
ac04e46
Merge branch '2.15' into 2787-allow-mixins-for-enums
JooHyukKim Mar 27, 2023
caac7f5
Remove unused import
JooHyukKim Mar 27, 2023
b8447f7
Merge branch '2787-allow-mixins-for-enums' of https://github.com/JooH…
JooHyukKim Mar 27, 2023
89d80de
Update EnumResolver.java
JooHyukKim Mar 27, 2023
4440da0
Tidy up comments and control flow
JooHyukKim Mar 28, 2023
e132e9d
Merge remote-tracking branch 'upstream/2.15' into 2787-allow-mixins-f…
JooHyukKim Apr 6, 2023
244a871
Simplify method signatures (apply reviews)
JooHyukKim Apr 6, 2023
baa9c8c
Merge branch '2.15' into 2787-allow-mixins-for-enums
JooHyukKim Apr 9, 2023
5143724
Move removing self-referencing enum fields
JooHyukKim Apr 15, 2023
ad73809
Merge remote-tracking branch 'upstream/2.15' into 2787-allow-mixins-f…
JooHyukKim Apr 15, 2023
a20d61d
Clean up EnumResolver
JooHyukKim Apr 15, 2023
d3a57ee
Clean up JacksonAnnotationIntrospector
JooHyukKim Apr 15, 2023
872dd10
Update EnumResolver.java
JooHyukKim Apr 15, 2023
8ae8ff4
Implement JacksonAnnotationIntrospector
JooHyukKim Mar 23, 2023
e89a778
Merge branch '2787-allow-mixins-for-enums' of https://github.com/JooH…
JooHyukKim Mar 23, 2023
abbee55
Allow construction of EnumResolver for mixin class
JooHyukKim Mar 18, 2023
d4d56e2
improve JavaDoc
JooHyukKim Mar 18, 2023
c07dd2f
Fix documentation
JooHyukKim Mar 18, 2023
47788f0
Remove mixin from EnumResolver
JooHyukKim Mar 23, 2023
99cf1f7
Add MapperConfig as first parameter
JooHyukKim Mar 24, 2023
0669e3c
Filter self referencing properties with JsonFormat.Shape is `OBJECT`
JooHyukKim Mar 27, 2023
1bd982a
Add more description
JooHyukKim Mar 27, 2023
833f6c4
Remove unused import
JooHyukKim Mar 27, 2023
dca4506
Update EnumResolver.java
JooHyukKim Mar 27, 2023
e3f7b1b
Tidy up comments and control flow
JooHyukKim Mar 28, 2023
ac2af2b
Simplify method signatures (apply reviews)
JooHyukKim Apr 6, 2023
aa589e5
Move removing self-referencing enum fields
JooHyukKim Apr 15, 2023
50570b9
Clean up JacksonAnnotationIntrospector
JooHyukKim Apr 15, 2023
f91657c
Clean up changes
JooHyukKim Apr 24, 2023
c78a0e4
Merge branch '2787-allow-mixins-for-enums' of https://github.com/JooH…
JooHyukKim Apr 24, 2023
5d98b69
Update EnumResolver.java
JooHyukKim Apr 24, 2023
5a85d0b
Merge remote-tracking branch 'upstream/2.16' into 2787-allow-mixins-f…
JooHyukKim May 4, 2023
3b89923
Modify JavaDoc
JooHyukKim May 5, 2023
db48e04
Squashed commit of the following:
JooHyukKim May 18, 2023
6bbf646
Revert "Squashed commit of the following:"
JooHyukKim May 18, 2023
f68e42b
Merge branch 'FasterXML:2.16' into 2787-allow-mixins-for-enums
JooHyukKim May 18, 2023
24f3f45
Merge remote-tracking branch 'upstream/2.16' into 2787-allow-mixins-f…
JooHyukKim Jun 14, 2023
1f5baf6
Apply review, improve `JacksonAnnotationIntropector` implementation l…
JooHyukKim Jun 14, 2023
426fef5
Improve JavaDoc wrt #2787
JooHyukKim Jun 14, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,26 @@ public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[]
return names;
}

/**
* Finds the explicitly defined name of the given set of {@code Enum} values, if any.
* The method overwrites entries in the incoming {@code names} array with the explicit
* names found, if any, leaving other entries unmodified.
*
* @param config the mapper configuration to use
* @param enumValues the set of {@code Enum} values to find the explicit names for
* @param names the matching declared names of enumeration values (with indexes matching
* {@code enumValues} entries)
* @param annotatedClass the annotated class for which to find the explicit names
*
* @return an array of names to use (possibly {@code names} passed as argument)
*
* @since 2.16
*/
public String[] findEnumValues(MapperConfig<?> config, Enum<?>[] enumValues, String[] names,
AnnotatedClass annotatedClass){
return names;
}

/**
* Method that is related to {@link #findEnumValues} but is called to check if
* there are alternative names (aliased) that can be accepted for entries, in
Expand All @@ -1121,6 +1141,25 @@ public void findEnumAliases(Class<?> enumType, Enum<?>[] enumValues, String[][]
;
}


/**
* Method that is called to check if there are alternative names (aliases) that can be accepted for entries
* in addition to primary names that were introspected earlier, related to {@link #findEnumValues}.
* These aliases should be returned in {@code String[][] aliases} passed in as argument.
* The {@code aliases.length} is expected to match the number of {@code Enum} values.
*
* @param config The configuration of the mapper
* @param enumValues The values of the enumeration
* @param aliases (in/out) Pre-allocated array where aliases found, if any, may be added (in indexes
* matching those of {@code enumValues})
* @param annotatedClass The annotated class of the enumeration type
*
* @since 2.16
*/
public void findEnumAliases(MapperConfig<?> config, Enum<?>[] enumValues, String[][] aliases,
AnnotatedClass annotatedClass) {
return;
}
/**
* Finds the Enum value that should be considered the default value, if possible.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2428,9 +2428,7 @@ protected EnumResolver constructEnumResolver(Class<?> enumClass,
}
return EnumResolver.constructUsingMethod(config, enumClass, jvAcc);
}
// 14-Mar-2016, tatu: We used to check `DeserializationFeature.READ_ENUMS_USING_TO_STRING`
// here, but that won't do: it must be dynamically changeable...
return EnumResolver.constructFor(config, enumClass);
return EnumResolver.constructFor(config, beanDesc.getClassInfo());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ private void _addFieldMixIns(Class<?> mixInCls, Class<?> targetClass,

private boolean _isIncludableField(Field f)
{
// [databind#2787]: Allow `Enum` mixins
if (f.isEnumConstant()) {
cowtowncoder marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
// Most likely synthetic fields, if any, are to be skipped similar to methods
if (f.isSynthetic()) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,13 +625,28 @@ public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[
return names;
}

@Override
public String[] findEnumValues(MapperConfig<?> config, Enum<?>[] enumValues, String[] names,
AnnotatedClass annotatedClass) {
names = _secondary.findEnumValues(config, enumValues, names, annotatedClass);
names = _primary.findEnumValues(config, enumValues, names, annotatedClass);
return names;
}

@Override
public void findEnumAliases(Class<?> enumType, Enum<?>[] enumValues, String[][] aliases) {
// reverse order to give _primary higher precedence
_secondary.findEnumAliases(enumType, enumValues, aliases);
_primary.findEnumAliases(enumType, enumValues, aliases);
}

@Override
public void findEnumAliases(MapperConfig<?> config, Enum<?>[] enumConstants, String[][] aliases,
AnnotatedClass annotatedClass) {
_secondary.findEnumAliases(config, enumConstants, aliases, annotatedClass);
_primary.findEnumAliases(config, enumConstants, aliases, annotatedClass);
}

@Override
public Enum<?> findDefaultEnumValue(Class<Enum<?>> enumCls) {
Enum<?> en = _primary.findDefaultEnumValue(enumCls);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,31 @@ public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[]
return names;
}

@Override // since 2.16
public String[] findEnumValues(MapperConfig<?> config, Enum<?>[] enumValues, String[] names,
AnnotatedClass annotatedClass) {
Map<String, String> enumToPropertyMap = new LinkedHashMap<String, String>();
for (AnnotatedField field : annotatedClass.fields()) {
JsonProperty property = field.getAnnotation(JsonProperty.class);
JooHyukKim marked this conversation as resolved.
Show resolved Hide resolved
if (property != null) {
String propValue = property.value();
if (propValue != null && !propValue.isEmpty()) {
enumToPropertyMap.put(field.getName(), propValue);
}
}
}

// and then stitch them together if and as necessary
for (int i = 0, end = enumValues.length; i < end; ++i) {
String defName = enumValues[i].name();
String explValue = enumToPropertyMap.get(defName);
if (explValue != null) {
names[i] = explValue;
}
}
return names;
}
JooHyukKim marked this conversation as resolved.
Show resolved Hide resolved

@Override // since 2.11
public void findEnumAliases(Class<?> enumType, Enum<?>[] enumValues, String[][] aliasList)
{
Expand All @@ -264,6 +289,23 @@ public void findEnumAliases(Class<?> enumType, Enum<?>[] enumValues, String[][]
}
}

@Override
public void findEnumAliases(MapperConfig<?> config, Enum<?>[] enumValues, String[][] aliasList, AnnotatedClass annotatedClass)
{
HashMap<String, String[]> enumToAliasMap = new HashMap<>();
for (AnnotatedField field : annotatedClass.fields()) {
JsonAlias alias = field.getAnnotation(JsonAlias.class);
if (alias != null) {
enumToAliasMap.putIfAbsent(field.getName(), alias.value());
}
}

for (int i = 0, end = enumValues.length; i < end; ++i) {
Enum<?> enumValue = enumValues[i];
aliasList[i] = enumToAliasMap.getOrDefault(enumValue.name(), new String[]{});
}
}

JooHyukKim marked this conversation as resolved.
Show resolved Hide resolved
/**
* Finds the Enum value that should be considered the default value, if possible.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,10 @@ protected JsonSerializer<?> buildEnumSerializer(SerializationConfig config,
if (format.getShape() == JsonFormat.Shape.OBJECT) {
// one special case: suppress serialization of "getDeclaringClass()"...
((BasicBeanDescription) beanDesc).removeProperty("declaringClass");
// [databind#2787]: remove self-referencing enum fields introduced by annotation flattening of mixins
if (type.isEnumType()){
_removeEnumSelfReferences(((BasicBeanDescription) beanDesc));
}
// returning null will mean that eventually BeanSerializer gets constructed
return null;
}
Copy link
Member Author

@JooHyukKim JooHyukKim Apr 15, 2023

Choose a reason for hiding this comment

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

This is where JsonFomat.Shape.OBJECT related Enum handling is applied, so seems like right place?

Expand All @@ -1224,6 +1228,30 @@ protected JsonSerializer<?> buildEnumSerializer(SerializationConfig config,
return ser;
}

/**
* Helper method used for serialization {@link Enum} as {@link JsonFormat.Shape#OBJECT}. Removes any
* self-referencing properties from its bean description before it is transformed into a JSON Object
* as configured by {@link JsonFormat.Shape#OBJECT}.
* <p>
* Internally, this method iterates through {@link BeanDescription#findProperties()} and removes self.
*
* @param beanDesc the bean description to remove Enum properties from.
*
* @since 2.16
*/
private void _removeEnumSelfReferences(BasicBeanDescription beanDesc) {
Class<?> aClass = ClassUtil.findEnumType(beanDesc.getBeanClass());
Iterator<BeanPropertyDefinition> it = beanDesc.findProperties().iterator();
while (it.hasNext()) {
BeanPropertyDefinition property = it.next();
JavaType propType = property.getPrimaryType();
// is the property a self-reference?
if (propType.isEnumType() && propType.isTypeOrSubTypeOf(aClass)) {
it.remove();
}
}
}

/*
/**********************************************************
/* Other helper methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.databind.EnumNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;

/**
* Helper class used to resolve String values (either JSON Object field
Expand Down Expand Up @@ -76,6 +77,7 @@ protected EnumResolver(Class<Enum<?>> enumClass, Enum<?>[] enums,
* Enum value.
*
* @since 2.12
* @deprecated Since 2.16 use {@link #constructFor(DeserializationConfig, AnnotatedClass)} instead
*/
public static EnumResolver constructFor(DeserializationConfig config,
Class<?> enumCls) {
Expand All @@ -84,6 +86,7 @@ public static EnumResolver constructFor(DeserializationConfig config,

/**
* @since 2.15
* @deprecated Since 2.16 use {@link #_constructFor(DeserializationConfig, AnnotatedClass)} instead
*/
protected static EnumResolver _constructFor(DeserializationConfig config, Class<?> enumCls0)
{
Expand Down Expand Up @@ -115,6 +118,60 @@ protected static EnumResolver _constructFor(DeserializationConfig config, Class<
false);
}

/**
* Factory method for constructing an {@link EnumResolver} based on the given {@link DeserializationConfig} and
* {@link AnnotatedClass} of the enum to be resolved.
*
* @param config the deserialization configuration to use
* @param annotatedClass the annotated class of the enum to be resolved
* @return the constructed {@link EnumResolver}
*
* @since 2.16
*/
public static EnumResolver constructFor(DeserializationConfig config, AnnotatedClass annotatedClass) {
return _constructFor(config, annotatedClass);
}

/**
* Internal method for {@link #_constructFor(DeserializationConfig, AnnotatedClass)}.
*
* @since 2.16
*/
public static EnumResolver _constructFor(DeserializationConfig config, AnnotatedClass annotatedClass)
{
// prepare data
final AnnotationIntrospector ai = config.getAnnotationIntrospector();
final boolean isIgnoreCase = config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
final Class<?> enumCls0 = annotatedClass.getRawType();
final Class<Enum<?>> enumCls = _enumClass(enumCls0);
final Enum<?>[] enumConstants = _enumConstants(enumCls0);

// introspect
String[] names = ai.findEnumValues(config, enumConstants, new String[enumConstants.length], annotatedClass);
final String[][] allAliases = new String[names.length][];
ai.findEnumAliases(config, enumConstants, allAliases, annotatedClass);

// finally, build
HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
for (int i = 0, len = enumConstants.length; i < len; ++i) {
final Enum<?> enumValue = enumConstants[i];
String name = names[i];
if (name == null) {
name = enumValue.name();
}
map.put(name, enumValue);
String[] aliases = allAliases[i];
if (aliases != null) {
for (String alias : aliases) {
// Avoid overriding any primary names
map.putIfAbsent(alias, enumValue);
}
}
}
return new EnumResolver(enumCls, enumConstants, map,
_enumDefault(ai, enumCls), isIgnoreCase, false);
}

/**
* Factory method for constructing resolver that maps from Enum.toString() into
* Enum value
Expand Down
Loading