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 8 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 @@ -1704,11 +1704,18 @@ public JsonDeserializer<?> createEnumDeserializer(DeserializationContext ctxt,

// Need to consider @JsonValue if one found
if (deser == null) {
deser = new EnumDeserializer(constructEnumResolver(enumClass,
config, beanDesc.findJsonValueAccessor()),
Class<?> mixInClass = config.findMixInClassFor(enumClass);
JooHyukKim marked this conversation as resolved.
Show resolved Hide resolved
if (mixInClass == null) {
deser = new EnumDeserializer(
constructEnumResolver(enumClass, config, beanDesc.findJsonValueAccessor()),
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS),
constructEnumNamingStrategyResolver(config, enumClass, beanDesc.getClassInfo())
);
constructEnumNamingStrategyResolver(config, enumClass, beanDesc.getClassInfo()));
} else {
deser = new EnumDeserializer(
EnumResolver.constructForMixin(config, enumClass, mixInClass),
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS),
null);
}
JooHyukKim marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
107 changes: 107 additions & 0 deletions src/main/java/com/fasterxml/jackson/databind/util/EnumResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.*;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.MapperFeature;
Expand Down Expand Up @@ -255,6 +256,112 @@ protected static EnumResolver _constructUsingMethod(Class<?> enumCls0,
);
}


/**
* Factory method to allow mix-ins for Enum classes. Its implementation regarding overrides
* of property names and alias SHOULD work just like POJOs.
*
* @since 2.15
*/
public static EnumResolver constructForMixin(DeserializationConfig config,
Class<?> enumCls, Class<?> mixInCls) {
return _constructForMixIn(enumCls, mixInCls, config.getAnnotationIntrospector(),
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
}

/**
* @since 2.15
*/
protected static EnumResolver _constructForMixIn(Class<?> enumCls0, Class<?> mixInCls0, AnnotationIntrospector ai, boolean isIgnoreCase) {
final Class<Enum<?>> enumCls = _enumClass(enumCls0);
final Enum<?>[] enumConstants = _enumConstants(enumCls0);
String[] names = ai.findEnumValues(enumCls, enumConstants, new String[enumConstants.length]);
final String[][] allAliases = new String[names.length][];
ai.findEnumAliases(enumCls, enumConstants, allAliases);

HashMap<String, String> mixinPropertyMap = _findEnumNameToMixinPropertyMap(ai, mixInCls0);
HashMap<String, String> mixinAliasMap = _findEnumNameToMixinAliasMap(ai, mixInCls0);

HashMap<String, Enum<?>> map = new HashMap<String, Enum<?>>();
for (int i = 0, len = enumConstants.length; i < len; ++i) {
final Enum<?> enumValue = enumConstants[i];

// Property
String name = names[i];
if (name == null) {
name = enumValue.name();
}
if (mixinPropertyMap.get(enumValue.name()) != null) {
name = mixinPropertyMap.get(enumValue.name());
}
map.put(name, enumValue);

// Alias
if (mixinAliasMap.get(enumValue.name()) != null) {
map.put(mixinAliasMap.get(enumValue.name()), enumValue);
} else {
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);
}

/**
* @return Map with key as {@link Enum#name()} of mixin Enum class and value as alias.
*
* @since 2.15
*/
protected static HashMap<String, String> _findEnumNameToMixinAliasMap(AnnotationIntrospector ai, Class<?> mixInCls) {
final Class<Enum<?>> enumCls = _enumClass(mixInCls);
final Enum<?>[] enumConstants = _enumConstants(mixInCls);
String[] names = ai.findEnumValues(enumCls, enumConstants, new String[enumConstants.length]);

final String[][] allAliases = new String[names.length][];
ai.findEnumAliases(enumCls, enumConstants, allAliases);

HashMap<String, String> map = new HashMap<String, String>();
for (int i = 0, len = enumConstants.length; i < len; ++i) {
final Enum<?> enumValue = enumConstants[i];
String[] aliases = allAliases[i];
if (aliases != null) {
for (String alias : aliases) {
map.putIfAbsent(enumValue.name(), alias);
}
}
}
return map;
}

/**
* @return Map with key as {@link Enum#name()} of mixin Enum class and value as property name.
*
* @since 2.15
*/
protected static HashMap<String, String> _findEnumNameToMixinPropertyMap(AnnotationIntrospector ai, Class<?> mixInCls) {
final Class<Enum<?>> enumCls = _enumClass(mixInCls);
final Enum<?>[] enumConstants = _enumConstants(mixInCls);
String[] names = ai.findEnumValues(enumCls, enumConstants, new String[enumConstants.length]);

HashMap<String, String> map = new HashMap<String, String>();
for (int i = 0, len = enumConstants.length; i < len; ++i) {
final Enum<?> enumValue = enumConstants[i];
String name = names[i];
if (name != null) {
map.put(enumValue.name(), name);
}
}
return map;
}

public CompactStringObjectMap constructLookup() {
return CompactStringObjectMap.construct(_enumsById);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.fasterxml.jackson.databind.deser.enums;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;

public class EnumDeserMixin2787Test extends BaseMapTest {

static enum Enum2787 {
ITEM_A,

@JsonAlias({"B_ORIGIN_ALIAS_1", "B_ORIGIN_ALIAS_2"})
@JsonProperty("B_ORIGIN_PROP")
ITEM_B,

@JsonAlias({"C_ORIGIN_ALIAS"})
@JsonProperty("C_ORIGIN_PROP")
ITEM_C,

ITEM_ORIGIN
}

static enum EnumMixin2787 {
ITEM_A,

@JsonProperty("B_MIXIN_PROP")
ITEM_B,

@JsonAlias({"C_MIXIN_ALIAS_1", "C_MIXIN_ALIAS_2"})
@JsonProperty("C_MIXIN_PROP")
ITEM_C,

ITEM_MIXIN;

@Override
public String toString() {
return "SHOULD NOT USE WITH TO STRING";
}
}

static class EnumWrapper {
public Enum2787 value;
}

/*
/**********************************************************************
/* Test methods
/**********************************************************************
*/

protected final ObjectMapper MAPPER = jsonMapperBuilder().build();

public void testEnumDeserSuccess() throws Exception {
ObjectMapper mapper = MAPPER.addMixIn(Enum2787.class, EnumMixin2787.class);

Enum2787 result = mapper.readValue(q("B_MIXIN_PROP"), Enum2787.class);

assertEquals(Enum2787.ITEM_B, result);
}

public void testEnumDeserSuccessCaseInsensitive() throws Exception {
ObjectMapper mapper = MAPPER.addMixIn(Enum2787.class, EnumMixin2787.class)
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);

Enum2787 result = mapper.readValue(q("B_mIxIn_pRoP"), Enum2787.class);
assertEquals(Enum2787.ITEM_B, result);
}

public void testEnumDeserSuccessMissingFromMixIn() throws Exception {
ObjectMapper mapper = MAPPER.addMixIn(Enum2787.class, EnumMixin2787.class);

Enum2787 result = mapper.readValue(q("ITEM_ORIGIN"), Enum2787.class);

assertEquals(Enum2787.ITEM_ORIGIN, result);
}

public void testEnumDeserMixinFail() throws Exception {
ObjectMapper mapper = MAPPER.addMixIn(Enum2787.class, EnumMixin2787.class);

// fail for Bean property name
try {
mapper.readValue(q("tax10"), Enum2787.class);
fail("should not reach");
} catch (InvalidFormatException e) {
verifyException(e, "tax10", "not one of the values accepted for Enum class");
}

// fail for Bean's JsonProperty because overridden
try {
mapper.readValue(q("B_ORIGIN_PROP"), Enum2787.class);
fail("should not reach");
} catch (InvalidFormatException e) {
verifyException(e, "B_ORIGIN_PROP", "not one of the values accepted for Enum class");
}
}

public void testMixInItselfNonJsonProperty() throws Exception {
ObjectMapper mapper = MAPPER.addMixIn(Enum2787.class, EnumMixin2787.class);

EnumMixin2787 result = mapper.readValue(q("ITEM_MIXIN"), EnumMixin2787.class);

assertEquals(EnumMixin2787.ITEM_MIXIN, result);
}

public void testMixInValueForTargetClass() throws Exception {
ObjectMapper mapper = MAPPER.addMixIn(Enum2787.class, EnumMixin2787.class);

try {
mapper.readValue(q("tax30"), Enum2787.class);
} catch (InvalidFormatException e) {
verifyException(e, " String \"tax30\": not one of the values accepted for Enum class:");
}
}

public void testMixinOnEnumValuesThrowWhenUnknown() throws Exception {
ObjectMapper mapper = MAPPER.addMixIn(Enum2787.class, EnumMixin2787.class);

try {
mapper.readValue(q("should-not-exist"), Enum2787.class);
} catch (InvalidFormatException e) {
verifyException(e, "should-not-exist", "not one of the values accepted for Enum class");
}
}

public void testMixinForWrapper() throws Exception{
ObjectMapper mapper = MAPPER.addMixIn(Enum2787.class, EnumMixin2787.class);

EnumWrapper result = mapper.readValue(a2q("{'value': 'C_MIXIN_ALIAS_1'}"), EnumWrapper.class);

assertEquals(Enum2787.ITEM_C, result.value);
}
}

This file was deleted.