Skip to content

Add enum features into JsonFormat.Feature #3731

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 3 commits into from
Jan 20, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -1712,7 +1712,8 @@ public JsonDeserializer<?> createEnumDeserializer(DeserializationContext ctxt,
if (deser == null) {
deser = new EnumDeserializer(constructEnumResolver(enumClass,
config, beanDesc.findJsonValueAccessor()),
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS));
config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.CompactStringObjectMap;
import com.fasterxml.jackson.databind.util.EnumResolver;
import java.util.Optional;

/**
* Deserializer class that can deserialize instances of
Expand Down Expand Up @@ -53,6 +54,9 @@ public class EnumDeserializer

protected final Boolean _caseInsensitive;

private Boolean _useDefaultValueForUnknownEnum;
private Boolean _useNullForUnknownEnum;

/**
* Marker flag for cases where we expect actual integral value for Enum,
* based on {@code @JsonValue} (and equivalent) annotated accessor.
Expand All @@ -77,14 +81,16 @@ public EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive)
/**
* @since 2.9
*/
protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive)
Copy link
Member

Choose a reason for hiding this comment

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

Will need to leave old constructor for backwards-compatibility; I can add it after merging.

protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive, Boolean useDefaultValueForUnknownEnum, Boolean useNullForUnknownEnum)
{
super(base);
_lookupByName = base._lookupByName;
_enumsByIndex = base._enumsByIndex;
_enumDefaultValue = base._enumDefaultValue;
_caseInsensitive = caseInsensitive;
_isFromIntValue = base._isFromIntValue;
_useDefaultValueForUnknownEnum = useDefaultValueForUnknownEnum;
_useNullForUnknownEnum = useNullForUnknownEnum;
}

/**
Expand Down Expand Up @@ -146,23 +152,26 @@ public static JsonDeserializer<?> deserializerForNoArgsCreator(DeserializationCo
/**
* @since 2.9
*/
public EnumDeserializer withResolved(Boolean caseInsensitive) {
if (Objects.equals(_caseInsensitive, caseInsensitive)) {
public EnumDeserializer withResolved(Boolean caseInsensitive, Boolean useDefaultValueForUnknownEnum, Boolean useNullForUnknownEnum) {
if (Objects.equals(_caseInsensitive, caseInsensitive)
&& Objects.equals(_useDefaultValueForUnknownEnum, useDefaultValueForUnknownEnum)
&& Objects.equals(_useNullForUnknownEnum, useNullForUnknownEnum)) {
return this;
}
return new EnumDeserializer(this, caseInsensitive);
return new EnumDeserializer(this, caseInsensitive, useDefaultValueForUnknownEnum, useNullForUnknownEnum);
}

@Override // since 2.9
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
Boolean caseInsensitive = findFormatFeature(ctxt, property, handledType(),
JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
if (caseInsensitive == null) {
caseInsensitive = _caseInsensitive;
}
return withResolved(caseInsensitive);
Boolean caseInsensitive = Optional.ofNullable(findFormatFeature(ctxt, property, handledType(),
JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)).orElse(_caseInsensitive);
Boolean useDefaultValueForUnknownEnum = Optional.ofNullable(findFormatFeature(ctxt, property, handledType(),
JsonFormat.Feature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)).orElse(_useDefaultValueForUnknownEnum);
Boolean useNullForUnknownEnum = Optional.ofNullable(findFormatFeature(ctxt, property, handledType(),
JsonFormat.Feature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)).orElse(_useNullForUnknownEnum);
return withResolved(caseInsensitive, useDefaultValueForUnknownEnum, useNullForUnknownEnum);
}

/*
Expand Down Expand Up @@ -262,11 +271,10 @@ protected Object _fromInteger(JsonParser p, DeserializationContext ctxt,
if (index >= 0 && index < _enumsByIndex.length) {
return _enumsByIndex[index];
}
if ((_enumDefaultValue != null)
&& ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
if (useDefaultValueForUnknownEnum(ctxt)) {
return _enumDefaultValue;
}
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
if (!useNullForUnknownEnum(ctxt)) {
return ctxt.handleWeirdNumberValue(_enumClass(), index,
"index value outside legal index range [0..%s]",
_enumsByIndex.length-1);
Expand All @@ -291,11 +299,10 @@ private final Object _deserializeAltString(JsonParser p, DeserializationContext
if (name.isEmpty()) { // empty or blank
// 07-Jun-2021, tatu: [databind#3171] Need to consider Default value first
// (alas there's bit of duplication here)
if ((_enumDefaultValue != null)
&& ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
if (useDefaultValueForUnknownEnum(ctxt)) {
return _enumDefaultValue;
}
if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
if (useNullForUnknownEnum(ctxt)) {
return null;
}

Expand Down Expand Up @@ -346,11 +353,10 @@ private final Object _deserializeAltString(JsonParser p, DeserializationContext
}
}
}
if ((_enumDefaultValue != null)
&& ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
if (useDefaultValueForUnknownEnum(ctxt)) {
return _enumDefaultValue;
}
if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
if (useNullForUnknownEnum(ctxt)) {
return null;
}
return ctxt.handleWeirdStringValue(_enumClass(), name,
Expand Down Expand Up @@ -384,4 +390,15 @@ protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt)
}
return lookup;
}

private boolean useNullForUnknownEnum(DeserializationContext ctxt) {
return Boolean.TRUE.equals(_useNullForUnknownEnum)
|| ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
}

private boolean useDefaultValueForUnknownEnum(DeserializationContext ctxt) {
return (_enumDefaultValue != null)
&& (Boolean.TRUE.equals(_useDefaultValueForUnknownEnum)
|| ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fasterxml.jackson.databind.deser.enums;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.IOException;
import java.util.EnumSet;

Expand Down Expand Up @@ -36,6 +37,25 @@ protected static class StrictCaseBean {
public TestEnum value;
}

protected static class DefaultEnumBean {
@JsonFormat(with={ JsonFormat.Feature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE })
public MyEnum2352_3 value;
}

protected static class DefaultEnumSetBean {
@JsonFormat(with={ JsonFormat.Feature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE })
public EnumSet<MyEnum2352_3> value;
}

protected static class NullValueEnumBean {
@JsonFormat(with={ JsonFormat.Feature.READ_UNKNOWN_ENUM_VALUES_AS_NULL })
public MyEnum2352_3 value;
}

protected static class NullEnumSetBean {
@JsonFormat(with={ JsonFormat.Feature.READ_UNKNOWN_ENUM_VALUES_AS_NULL })
public EnumSet<MyEnum2352_3> value;
}

// for [databind#2352]: Support aliases on enum values
enum MyEnum2352_1 {
Expand Down Expand Up @@ -213,4 +233,56 @@ public void testEnumWithAliasAndDefaultForUnknownValueEnabled() throws Exception
MyEnum2352_3 multipleAliases2 = reader.readValue(q("multipleAliases2"));
assertEquals(MyEnum2352_3.C, multipleAliases2);
}

public void testEnumWithDefaultForUnknownValueEnabled() throws Exception {
final String JSON = a2q("{'value':'ok'}");

DefaultEnumBean pojo = READER_DEFAULT.forType(DefaultEnumBean.class)
.readValue(JSON);
assertEquals(MyEnum2352_3.B, pojo.value);
// including disabling acceptance
try {
READER_DEFAULT.forType(StrictCaseBean.class)
.readValue(JSON);
fail("Should not pass");
} catch (InvalidFormatException e) {
verifyException(e, "not one of the values accepted for Enum class");
verifyException(e, "[JACKSON, OK, RULES]");
}
}

Copy link
Member

Choose a reason for hiding this comment

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

In addition to testing single Enum property it'd be good to see if things work for EnumSets?

I don't remember if things might just work due to delegation, but it might be necessary to change EnumSetDeserializer. Adding a test would clarify this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added the test cases for EnumSet. Please have look if behaviour is expected.Thanks.

public void testEnumWithNullForUnknownValueEnabled() throws Exception {
final String JSON = a2q("{'value':'ok'}");

NullValueEnumBean pojo = READER_DEFAULT.forType(NullValueEnumBean.class)
.readValue(JSON);
assertNull(pojo.value);
// including disabling acceptance
try {
READER_DEFAULT.forType(StrictCaseBean.class)
.readValue(JSON);
fail("Should not pass");
} catch (InvalidFormatException e) {
verifyException(e, "not one of the values accepted for Enum class");
verifyException(e, "[JACKSON, OK, RULES]");
}
}

public void testEnumWithDefaultForUnknownValueEnumSet() throws Exception {
final String JSON = a2q("{'value':['ok']}");

DefaultEnumSetBean pojo = READER_DEFAULT.forType(DefaultEnumSetBean.class)
.readValue(JSON);
assertEquals(1, pojo.value.size());
assertTrue(pojo.value.contains(MyEnum2352_3.B));
}

public void testEnumWithNullForUnknownValueEnumSet() throws Exception {
final String JSON = a2q("{'value':['ok','B']}");

NullEnumSetBean pojo = READER_DEFAULT.forType(NullEnumSetBean.class)
.readValue(JSON);
assertEquals(1, pojo.value.size());
assertTrue(pojo.value.contains(MyEnum2352_3.B));
}
}