Case-insensitive and number-based enum deserialization are (unnecessarily) mutually exclusive · Issue #3638 · FasterXML/jackson-databind (original) (raw)

Not sure if this should be filed as a bug or a feature request, since the "expected" behavior is mostly left to interpretation, but I went with a bug report in the end. Let me know if that makes sense.

Describe the bug
Deserializing an enum field that is marked for case-insensitive deserialization does not work if the source value happens to be shaped as the string representation of a number, whereas the same value will be deserialized properly if case-insensitive deserialization is disabled.

Version information
2.12.4

To Reproduce
If you have a way to reproduce this with:

enum MyEnum { FIRST_MEMBER(0), SECOND_MEMBER(1);

private int index;

private MyEnum(int index) {
    this.index = index;
}

}

ACCEPT_CASE_INSENSITIVE_PROPERTIES: disabled

class MyClass {
    public MyEnum enumValue;
}

ACCEPT_CASE_INSENSITIVE_PROPERTIES: enabled

class MyClass {
    @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
    public MyEnum enumValue;
}

Json input ACCEPT_CASE_INSENSITIVE_PROPERTIES: disabled ACCEPT_CASE_INSENSITIVE_PROPERTIES: enabled
{ "enumValue": "FIRST_MEMBER" }
{ "enumValue": "first_member" }
{ "enumValue": 0 }
{ "enumValue": "0" } ❌ 👈

Both failures (❌) are the same:

com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `MyClass` from String <X> not one of the values accepted for Enum class: ...

Expected behavior
I would expect the case annotated with 👈 in the table above to successfully deserialize the value. It seems as if the reason why this is not the case for now is that the conditional branches branches that handle the case-insensitive parsing and the number-based enum parsing are mutually exclusive:

// [databind#1313]: Case insensitive enum deserialization
if (Boolean.TRUE.equals(_caseInsensitive)) {
Object match = lookup.findCaseInsensitive(name);
if (match != null) {
return match;
}
} else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
// [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
char c = name.charAt(0);
if (c >= '0' && c <= '9') {
try {
int index = Integer.parseInt(name);
if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
return ctxt.handleWeirdStringValue(_enumClass(), name,
"value looks like quoted Enum index, but `MapperFeature.ALLOW_COERCION_OF_SCALARS` prevents use"
);
}
if (index >= 0 && index < _enumsByIndex.length) {
return _enumsByIndex[index];
}
} catch (NumberFormatException e) {
// fine, ignore, was not an integer
}
}
}

Not sure if this is by-design or if there is a historical precedent that lead to this, but I don't see why the deserialization process couldn't rely on attempting the number-based parsing in the case where _caseInsensitive is true and the subsequent lookup failed to return any results.