Skip to content

Commit cf7ad0a

Browse files
committed
Start work on #2903
1 parent 7ba8e84 commit cf7ad0a

File tree

3 files changed

+224
-5
lines changed

3 files changed

+224
-5
lines changed

src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java

+26-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import com.fasterxml.jackson.databind.*;
1010
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
11+
import com.fasterxml.jackson.databind.cfg.CoercionAction;
1112
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
1213
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
1314
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
@@ -170,6 +171,11 @@ public LogicalType logicalType() {
170171
return LogicalType.Enum;
171172
}
172173

174+
@Override // since 2.12
175+
public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
176+
return _enumDefaultValue;
177+
}
178+
173179
@Override
174180
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
175181
{
@@ -235,13 +241,29 @@ protected Object _fromString(JsonParser p, DeserializationContext ctxt,
235241
*/
236242

237243
private final Object _deserializeAltString(JsonParser p, DeserializationContext ctxt,
238-
CompactStringObjectMap lookup, String name) throws IOException
244+
CompactStringObjectMap lookup, String nameOrig) throws IOException
239245
{
240-
name = name.trim();
241-
if (name.length() == 0) {
242-
if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
246+
String name = nameOrig.trim();
247+
if (name.length() == 0) { // empty or blank
248+
CoercionAction act;
249+
if (nameOrig.length() == 0) {
250+
act = _findCoercionFromEmptyString(ctxt);
251+
act = _checkCoercionFail(ctxt, act, handledType(), nameOrig,
252+
"empty String (\"\")");
253+
} else {
254+
act = _findCoercionFromBlankString(ctxt);
255+
act = _checkCoercionFail(ctxt, act, handledType(), nameOrig,
256+
"blank String (all whitespace)");
257+
}
258+
switch (act) {
259+
case AsEmpty:
260+
case TryConvert:
243261
return getEmptyValue(ctxt);
262+
case AsNull:
263+
default: // Fail already handled earlier
244264
}
265+
return null;
266+
// if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
245267
} else {
246268
// [databind#1313]: Case insensitive enum deserialization
247269
if (Boolean.TRUE.equals(_caseInsensitive)) {

src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java

-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ protected T _deserializeFromString(JsonParser p, DeserializationContext ctxt)
264264
if ((inst != null) && inst.canCreateFromString()) {
265265
return (T) inst.createFromString(ctxt, value);
266266
}
267-
268267
if (value.length() == 0) {
269268
final CoercionAction act = ctxt.findCoercionAction(logicalType(), rawTargetType,
270269
CoercionInputShape.EmptyString);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package com.fasterxml.jackson.databind.convert;
2+
3+
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
4+
import com.fasterxml.jackson.core.JsonProcessingException;
5+
6+
import com.fasterxml.jackson.databind.*;
7+
import com.fasterxml.jackson.databind.cfg.CoercionAction;
8+
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
9+
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
10+
import com.fasterxml.jackson.databind.type.LogicalType;
11+
12+
public class CoerceEnumTest extends BaseMapTest
13+
{
14+
protected enum EnumCoerce {
15+
A, B, C,
16+
17+
// since 2.12 defines Enum "empty" to be default value (if defined),
18+
// or `null` (if not defined), define default...
19+
@JsonEnumDefaultValue
20+
DEFAULT
21+
;
22+
}
23+
24+
private final ObjectMapper MAPPER = newJsonMapper();
25+
26+
private final String JSON_EMPTY = quote("");
27+
private final String JSON_BLANK = quote(" ");
28+
29+
private final EnumCoerce ENUM_DEFAULT = EnumCoerce.DEFAULT;
30+
31+
/*
32+
/********************************************************
33+
/* Test methods, from empty String
34+
/********************************************************
35+
*/
36+
37+
public void testLegacyDefaults() throws Exception
38+
{
39+
// first, verify default settings which do not accept empty String:
40+
assertFalse(MAPPER.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS));
41+
}
42+
43+
public void testEnumFromEmptyGlobalConfig() throws Exception {
44+
_testEnumFromEmptyGlobalConfig(CoercionInputShape.EmptyString, JSON_EMPTY, null);
45+
}
46+
47+
public void testEnumFromEmptyLogicalTypeConfig() throws Exception {
48+
_testEnumFromEmptyLogicalTypeConfig(CoercionInputShape.EmptyString, JSON_EMPTY, null);
49+
}
50+
51+
public void testEnumFromEmptyPhysicalTypeConfig() throws Exception {
52+
_testEnumFromEmptyPhysicalTypeConfig(CoercionInputShape.EmptyString, JSON_EMPTY, null);
53+
}
54+
55+
/*
56+
/********************************************************
57+
/* Test methods, from blank String
58+
/********************************************************
59+
*/
60+
61+
public void testEnumFromBlankGlobalConfig() throws Exception {
62+
_testEnumFromEmptyGlobalConfig(CoercionInputShape.EmptyString, JSON_BLANK, Boolean.TRUE);
63+
}
64+
65+
public void testEnumFromBlankLogicalTypeConfig() throws Exception {
66+
_testEnumFromEmptyLogicalTypeConfig(CoercionInputShape.EmptyString, JSON_BLANK, Boolean.TRUE);
67+
}
68+
69+
public void testEnumFromBlankPhysicalTypeConfig() throws Exception {
70+
_testEnumFromEmptyPhysicalTypeConfig(CoercionInputShape.EmptyString, JSON_BLANK, Boolean.TRUE);
71+
}
72+
73+
/*
74+
/********************************************************
75+
/* Test methods, from numeric index
76+
/********************************************************
77+
*/
78+
79+
/*
80+
/********************************************************
81+
/* Second-level helper methods
82+
/********************************************************
83+
*/
84+
85+
private void _testEnumFromEmptyGlobalConfig(final CoercionInputShape shape, final String json,
86+
Boolean allowEmpty)
87+
throws Exception
88+
{
89+
ObjectMapper mapper;
90+
91+
// First, coerce to null
92+
mapper = newJsonMapper();
93+
mapper.coercionConfigDefaults().setCoercion(shape, CoercionAction.AsNull)
94+
.setAcceptBlankAsEmpty(allowEmpty);
95+
assertNull(_verifyFromEmptyPass(mapper, json));
96+
97+
// Then coerce as empty
98+
mapper = newJsonMapper();
99+
mapper.coercionConfigDefaults().setCoercion(shape, CoercionAction.AsEmpty)
100+
.setAcceptBlankAsEmpty(allowEmpty);
101+
EnumCoerce b = _verifyFromEmptyPass(mapper, json);
102+
assertEquals(ENUM_DEFAULT, b);
103+
104+
// and finally, "try convert", which for Enums is same as "empty" (default)
105+
mapper = newJsonMapper();
106+
mapper.coercionConfigDefaults().setCoercion(shape, CoercionAction.TryConvert)
107+
.setAcceptBlankAsEmpty(allowEmpty);
108+
assertEquals(ENUM_DEFAULT, _verifyFromEmptyPass(mapper, json));
109+
}
110+
111+
private void _testEnumFromEmptyLogicalTypeConfig(final CoercionInputShape shape, final String json,
112+
Boolean allowEmpty)
113+
throws Exception
114+
{
115+
ObjectMapper mapper;
116+
117+
// First, coerce to null
118+
mapper = newJsonMapper();
119+
mapper.coercionConfigFor(LogicalType.Enum).setCoercion(shape, CoercionAction.AsNull)
120+
.setAcceptBlankAsEmpty(allowEmpty);
121+
assertNull(_verifyFromEmptyPass(mapper, json));
122+
123+
// Then coerce as empty
124+
mapper = newJsonMapper();
125+
mapper.coercionConfigFor(LogicalType.Enum).setCoercion(shape, CoercionAction.AsEmpty)
126+
.setAcceptBlankAsEmpty(allowEmpty);
127+
EnumCoerce b = _verifyFromEmptyPass(mapper, json);
128+
assertNotNull(b);
129+
130+
// But also make fail again with 2-level settings
131+
mapper = newJsonMapper();
132+
mapper.coercionConfigDefaults().setCoercion(shape, CoercionAction.AsNull)
133+
.setAcceptBlankAsEmpty(allowEmpty);
134+
mapper.coercionConfigFor(LogicalType.Enum).setCoercion(shape,
135+
CoercionAction.Fail);
136+
_verifyFromEmptyFail(mapper, json);
137+
}
138+
139+
private void _testEnumFromEmptyPhysicalTypeConfig(final CoercionInputShape shape, final String json,
140+
Boolean allowEmpty)
141+
throws Exception
142+
{
143+
ObjectMapper mapper;
144+
145+
// First, coerce to null
146+
mapper = newJsonMapper();
147+
mapper.coercionConfigFor(EnumCoerce.class).setCoercion(shape, CoercionAction.AsNull)
148+
.setAcceptBlankAsEmpty(allowEmpty);
149+
assertNull(_verifyFromEmptyPass(mapper, json));
150+
151+
// Then coerce as empty
152+
mapper = newJsonMapper();
153+
mapper.coercionConfigFor(EnumCoerce.class).setCoercion(shape, CoercionAction.AsEmpty)
154+
.setAcceptBlankAsEmpty(allowEmpty);
155+
EnumCoerce b = _verifyFromEmptyPass(mapper, json);
156+
assertNotNull(b);
157+
158+
// But also make fail again with 2-level settings, with physical having precedence
159+
mapper = newJsonMapper();
160+
mapper.coercionConfigFor(LogicalType.Enum).setCoercion(shape, CoercionAction.AsEmpty)
161+
.setAcceptBlankAsEmpty(allowEmpty);
162+
mapper.coercionConfigFor(EnumCoerce.class).setCoercion(shape, CoercionAction.Fail);
163+
_verifyFromEmptyFail(mapper, json);
164+
}
165+
166+
/*
167+
/********************************************************
168+
/* Verification helper methods
169+
/********************************************************
170+
*/
171+
172+
private EnumCoerce _verifyFromEmptyPass(ObjectMapper m, String json) throws Exception {
173+
return _verifyFromEmptyPass(m.reader(), json);
174+
}
175+
176+
private EnumCoerce _verifyFromEmptyPass(ObjectReader r, String json) throws Exception
177+
{
178+
return r.forType(EnumCoerce.class)
179+
.readValue(json);
180+
}
181+
182+
private void _verifyFromEmptyFail(ObjectMapper m, String json) throws Exception
183+
{
184+
try {
185+
m.readValue(json, EnumCoerce.class);
186+
fail("Should not accept Empty/Blank String for Enum with passed settings");
187+
} catch (MismatchedInputException e) {
188+
_verifyFailMessage(e);
189+
}
190+
}
191+
192+
private void _verifyFailMessage(JsonProcessingException e)
193+
{
194+
verifyException(e, "Cannot coerce ");
195+
verifyException(e, " empty String ", " blank String ");
196+
assertValidLocation(e.getLocation());
197+
}
198+
}

0 commit comments

Comments
 (0)