Skip to content

Commit 7f7a2fa

Browse files
committed
Almost completing #2903, add checks for Integer->Enum coercion
1 parent 33521f0 commit 7f7a2fa

File tree

6 files changed

+183
-57
lines changed

6 files changed

+183
-57
lines changed

src/main/java/com/fasterxml/jackson/databind/cfg/CoercionConfigs.java

+17-7
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,26 @@ public CoercionAction findCoercion(DeserializationConfig config,
191191
}
192192

193193
// Otherwise there are some legacy features that can provide answer
194-
if (inputShape == CoercionInputShape.EmptyArray) {
194+
switch (inputShape) {
195+
case EmptyArray:
195196
// Default for setting is false
196197
return config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT) ?
197198
CoercionAction.AsNull : CoercionAction.Fail;
198-
}
199-
if ((inputShape == CoercionInputShape.Float)
200-
&& (targetType == LogicalType.Integer)) {
201-
// Default for setting in 2.x is true
202-
return config.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT) ?
203-
CoercionAction.TryConvert : CoercionAction.Fail;
199+
case Float:
200+
if (targetType == LogicalType.Integer) {
201+
// Default for setting in 2.x is true
202+
return config.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT) ?
203+
CoercionAction.TryConvert : CoercionAction.Fail;
204+
}
205+
break;
206+
case Integer:
207+
if (targetType == LogicalType.Enum) {
208+
if (config.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
209+
return CoercionAction.Fail;
210+
}
211+
}
212+
break;
213+
default:
204214
}
205215

206216
// classic scalars are numbers, booleans; but date/time also considered

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

+47-20
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.fasterxml.jackson.databind.*;
1010
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
1111
import com.fasterxml.jackson.databind.cfg.CoercionAction;
12+
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
1213
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
1314
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
1415
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
@@ -188,26 +189,7 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
188189

189190
// But let's consider int acceptable as well (if within ordinal range)
190191
if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
191-
// ... unless told not to do that
192-
int index = p.getIntValue();
193-
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
194-
return ctxt.handleWeirdNumberValue(_enumClass(), index,
195-
"not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"
196-
);
197-
}
198-
if (index >= 0 && index < _enumsByIndex.length) {
199-
return _enumsByIndex[index];
200-
}
201-
if ((_enumDefaultValue != null)
202-
&& ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
203-
return _enumDefaultValue;
204-
}
205-
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
206-
return ctxt.handleWeirdNumberValue(_enumClass(), index,
207-
"index value outside legal index range [0..%s]",
208-
_enumsByIndex.length-1);
209-
}
210-
return null;
192+
return _fromInteger(p, ctxt, p.getIntValue());
211193
}
212194

213195
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
@@ -234,6 +216,51 @@ protected Object _fromString(JsonParser p, DeserializationContext ctxt,
234216
return result;
235217
}
236218

219+
protected Object _fromInteger(JsonParser p, DeserializationContext ctxt,
220+
int index)
221+
throws IOException
222+
{
223+
final CoercionAction act = ctxt.findCoercionAction(logicalType(), handledType(),
224+
CoercionInputShape.Integer);
225+
226+
// First, check legacy setting for slightly different message
227+
if (act == CoercionAction.Fail) {
228+
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
229+
return ctxt.handleWeirdNumberValue(_enumClass(), index,
230+
"not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"
231+
);
232+
}
233+
// otherwise this will force failure with new setting
234+
_checkCoercionFail(ctxt, act, handledType(), index,
235+
"Integer value ("+index+")");
236+
}
237+
switch (act) {
238+
case AsNull:
239+
return null;
240+
case AsEmpty:
241+
return getEmptyValue(ctxt);
242+
case TryConvert:
243+
default:
244+
}
245+
if (index >= 0 && index < _enumsByIndex.length) {
246+
return _enumsByIndex[index];
247+
}
248+
if ((_enumDefaultValue != null)
249+
&& ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
250+
return _enumDefaultValue;
251+
}
252+
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
253+
return ctxt.handleWeirdNumberValue(_enumClass(), index,
254+
"index value outside legal index range [0..%s]",
255+
_enumsByIndex.length-1);
256+
}
257+
return null;
258+
}
259+
/*
260+
return _checkCoercionFail(ctxt, act, rawTargetType, value,
261+
"empty String (\"\")");
262+
*/
263+
237264
/*
238265
/**********************************************************
239266
/* Internal helper methods

src/test/java/com/fasterxml/jackson/databind/BaseMapTest.java

-12
Original file line numberDiff line numberDiff line change
@@ -371,16 +371,4 @@ protected byte[] utf8Bytes(String str) {
371371
throw new IllegalArgumentException(e);
372372
}
373373
}
374-
375-
protected static String aposToQuotes(String json) {
376-
return json.replace("'", "\"");
377-
}
378-
379-
protected static String a2q(String json) {
380-
return json.replace("'", "\"");
381-
}
382-
383-
protected static String quotesToApos(String json) {
384-
return json.replace("\"", "'");
385-
}
386374
}

src/test/java/com/fasterxml/jackson/databind/BaseTest.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,19 @@ protected byte[] encodeInUTF32BE(String input)
401401
return result;
402402
}
403403

404-
public String quote(String str) {
404+
public String q(String str) {
405405
return '"'+str+'"';
406406
}
407+
408+
public String quote(String str) {
409+
return q(str);
410+
}
411+
412+
protected static String a2q(String json) {
413+
return json.replace("'", "\"");
414+
}
415+
416+
protected static String aposToQuotes(String json) {
417+
return a2q(json);
418+
}
407419
}

src/test/java/com/fasterxml/jackson/databind/convert/CoerceEnumTest.java

+102-17
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,84 @@ public void testEnumFromBlankPhysicalTypeConfig() throws Exception {
7676
/********************************************************
7777
*/
7878

79+
public void testEnumFromIntFailLegacy() throws Exception
80+
{
81+
final ObjectReader r = MAPPER.readerFor(EnumCoerce.class);
82+
83+
// by default, should be ok
84+
EnumCoerce result = r.readValue("1");
85+
assertEquals(EnumCoerce.values()[1], result);
86+
87+
try {
88+
r.with(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
89+
.readValue("1");
90+
fail("Should not pass");
91+
} catch (Exception e) {
92+
verifyException(e, "not allowed to deserialize Enum value out of");
93+
}
94+
}
95+
96+
public void testEnumFromIntAsNull() throws Exception
97+
{
98+
final String json = "1";
99+
ObjectMapper mapper;
100+
101+
mapper = _globMapper(CoercionInputShape.Integer, CoercionAction.AsNull, false);
102+
assertNull(_readEnumPass(mapper, json));
103+
mapper = _logMapper(LogicalType.Enum,
104+
CoercionInputShape.Integer, CoercionAction.AsNull, false);
105+
assertNull(_readEnumPass(mapper, json));
106+
mapper = _physMapper(EnumCoerce.class,
107+
CoercionInputShape.Integer, CoercionAction.AsNull, false);
108+
assertNull(_readEnumPass(mapper, json));
109+
}
110+
111+
public void testEnumFromIntAsEmpty() throws Exception
112+
{
113+
final String json = "1";
114+
ObjectMapper mapper;
115+
116+
mapper = _globMapper(CoercionInputShape.Integer, CoercionAction.AsEmpty, false);
117+
assertEquals(ENUM_DEFAULT, _readEnumPass(mapper, json));
118+
mapper = _logMapper(LogicalType.Enum,
119+
CoercionInputShape.Integer, CoercionAction.AsEmpty, false);
120+
assertEquals(ENUM_DEFAULT, _readEnumPass(mapper, json));
121+
mapper = _physMapper(EnumCoerce.class,
122+
CoercionInputShape.Integer, CoercionAction.AsEmpty, false);
123+
assertEquals(ENUM_DEFAULT, _readEnumPass(mapper, json));
124+
}
125+
126+
public void testEnumFromIntCoerce() throws Exception
127+
{
128+
final String json = "1";
129+
ObjectMapper mapper;
130+
EnumCoerce exp = EnumCoerce.B; // entry[1]
131+
132+
mapper = _globMapper(CoercionInputShape.Integer, CoercionAction.TryConvert, false);
133+
assertEquals(exp, _readEnumPass(mapper, json));
134+
mapper = _logMapper(LogicalType.Enum,
135+
CoercionInputShape.Integer, CoercionAction.TryConvert, false);
136+
assertEquals(exp, _readEnumPass(mapper, json));
137+
mapper = _physMapper(EnumCoerce.class,
138+
CoercionInputShape.Integer, CoercionAction.TryConvert, false);
139+
assertEquals(exp, _readEnumPass(mapper, json));
140+
}
141+
142+
public void testEnumFromIntFailCoercionConfig() throws Exception
143+
{
144+
final String json = "1";
145+
ObjectMapper mapper;
146+
147+
mapper = _globMapper(CoercionInputShape.Integer, CoercionAction.Fail, false);
148+
_verifyFromIntegerFail(mapper, json);
149+
mapper = _logMapper(LogicalType.Enum,
150+
CoercionInputShape.Integer, CoercionAction.Fail, false);
151+
_verifyFromIntegerFail(mapper, json);
152+
mapper = _physMapper(EnumCoerce.class,
153+
CoercionInputShape.Integer, CoercionAction.Fail, false);
154+
_verifyFromIntegerFail(mapper, json);
155+
}
156+
79157
/*
80158
/********************************************************
81159
/* Second-level helper methods
@@ -90,16 +168,16 @@ private void _testEnumFromEmptyGlobalConfig(final CoercionInputShape shape, fina
90168

91169
// First, coerce to null
92170
mapper = _globMapper(shape, CoercionAction.AsNull, allowEmpty);
93-
assertNull(_verifyFromEmptyPass(mapper, json));
171+
assertNull(_readEnumPass(mapper, json));
94172

95173
// Then coerce as empty
96174
mapper = _globMapper(shape, CoercionAction.AsEmpty, allowEmpty);
97-
EnumCoerce b = _verifyFromEmptyPass(mapper, json);
175+
EnumCoerce b = _readEnumPass(mapper, json);
98176
assertEquals(ENUM_DEFAULT, b);
99177

100178
// and finally, "try convert", which for Enums is same as "empty" (default)
101179
mapper = _globMapper(shape, CoercionAction.TryConvert, allowEmpty);
102-
assertEquals(ENUM_DEFAULT, _verifyFromEmptyPass(mapper, json));
180+
assertEquals(ENUM_DEFAULT, _readEnumPass(mapper, json));
103181
}
104182

105183
private void _testEnumFromEmptyLogicalTypeConfig(final CoercionInputShape shape, final String json,
@@ -111,17 +189,17 @@ private void _testEnumFromEmptyLogicalTypeConfig(final CoercionInputShape shape,
111189

112190
// First, coerce to null
113191
mapper = _logMapper(LogicalType.Enum, shape, CoercionAction.AsNull, allowEmpty);
114-
b = _verifyFromEmptyPass(mapper, json);
192+
b = _readEnumPass(mapper, json);
115193
assertNull(b);
116194

117195
// Then coerce as empty
118196
mapper = _logMapper(LogicalType.Enum, shape, CoercionAction.AsEmpty, allowEmpty);
119-
b = _verifyFromEmptyPass(mapper, json);
197+
b = _readEnumPass(mapper, json);
120198
assertEquals(ENUM_DEFAULT, b);
121199

122200
// and with TryConvert (for enums same as empty)
123201
mapper = _logMapper(LogicalType.Enum, shape, CoercionAction.TryConvert, allowEmpty);
124-
b = _verifyFromEmptyPass(mapper, json);
202+
b = _readEnumPass(mapper, json);
125203
assertEquals(ENUM_DEFAULT, b);
126204

127205
// But also make fail again with 2-level settings
@@ -142,16 +220,16 @@ private void _testEnumFromEmptyPhysicalTypeConfig(final CoercionInputShape shape
142220

143221
// First, coerce to null
144222
mapper = _physMapper(EnumCoerce.class, shape, CoercionAction.AsNull, allowEmpty);
145-
b = _verifyFromEmptyPass(mapper, json);
223+
b = _readEnumPass(mapper, json);
146224
assertNull(b);
147225

148226
// Then coerce as empty
149227
mapper = _physMapper(EnumCoerce.class, shape, CoercionAction.AsEmpty, allowEmpty);
150-
b = _verifyFromEmptyPass(mapper, json);
228+
b = _readEnumPass(mapper, json);
151229
assertEquals(ENUM_DEFAULT, b);
152230

153231
mapper = _physMapper(EnumCoerce.class, shape, CoercionAction.TryConvert, allowEmpty);
154-
b = _verifyFromEmptyPass(mapper, json);
232+
b = _readEnumPass(mapper, json);
155233
assertEquals(ENUM_DEFAULT, b);
156234

157235
// But also make fail again with 2-level settings, with physical having precedence
@@ -201,11 +279,11 @@ private ObjectMapper _physMapper(Class<?> type, CoercionInputShape shape, Coerci
201279
/********************************************************
202280
*/
203281

204-
private EnumCoerce _verifyFromEmptyPass(ObjectMapper m, String json) throws Exception {
205-
return _verifyFromEmptyPass(m.reader(), json);
282+
private EnumCoerce _readEnumPass(ObjectMapper m, String json) throws Exception {
283+
return _readEnumPass(m.reader(), json);
206284
}
207285

208-
private EnumCoerce _verifyFromEmptyPass(ObjectReader r, String json) throws Exception
286+
private EnumCoerce _readEnumPass(ObjectReader r, String json) throws Exception
209287
{
210288
return r.forType(EnumCoerce.class)
211289
.readValue(json);
@@ -217,14 +295,21 @@ private void _verifyFromEmptyFail(ObjectMapper m, String json) throws Exception
217295
m.readValue(json, EnumCoerce.class);
218296
fail("Should not accept Empty/Blank String for Enum with passed settings");
219297
} catch (MismatchedInputException e) {
220-
_verifyFailMessage(e);
298+
verifyException(e, "Cannot coerce ");
299+
verifyException(e, " empty String ", " blank String ");
300+
assertValidLocation(e.getLocation());
221301
}
222302
}
223303

224-
private void _verifyFailMessage(JsonProcessingException e)
304+
private void _verifyFromIntegerFail(ObjectMapper m, String json) throws Exception
225305
{
226-
verifyException(e, "Cannot coerce ");
227-
verifyException(e, " empty String ", " blank String ");
228-
assertValidLocation(e.getLocation());
306+
try {
307+
m.readValue(json, EnumCoerce.class);
308+
fail("Should not accept Empty/Blank String for Enum with passed settings");
309+
} catch (MismatchedInputException e) {
310+
verifyException(e, "Cannot coerce Integer value (");
311+
verifyException(e, "but could if coercion was enabled");
312+
assertValidLocation(e.getLocation());
313+
}
229314
}
230315
}

src/test/java/com/fasterxml/jackson/databind/jsonschema/TestGenerateJsonSchema.java

+4
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,8 @@ public void testNumberTypes() throws Exception
247247
+"'bigInt':{'type':'integer'}}}";
248248
assertEquals(EXP, json);
249249
}
250+
251+
protected static String quotesToApos(String json) {
252+
return json.replace("\"", "'");
253+
}
250254
}

0 commit comments

Comments
 (0)