Skip to content

Commit

Permalink
Add enum features into JsonFormat.Feature (#3731)
Browse files Browse the repository at this point in the history
Co-authored-by: Ajay Siwach <[email protected]>
  • Loading branch information
Siwach16 and Ajay Siwach authored Jan 20, 2023
1 parent abadc05 commit 678f31c
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1706,7 +1706,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)
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]");
}
}

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));
}
}

0 comments on commit 678f31c

Please sign in to comment.