Skip to content

Commit 0b6cb9b

Browse files
committed
Add DeserializationProblemHandler.handleWeirdStringValue(...) to allow recovering from JSON String value problems
1 parent 888c6b9 commit 0b6cb9b

File tree

11 files changed

+213
-108
lines changed

11 files changed

+213
-108
lines changed

src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java

Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,46 @@ public Object handleWeirdKey(Class<?> keyClass, String keyValue,
878878
throw weirdKeyException(keyClass, keyValue, msg);
879879
}
880880

881+
/**
882+
* Method that deserializers should call if they encounter a String value
883+
* that can not be converted to target property type, in cases where some
884+
* String values could be acceptable (either with different settings,
885+
* or different value).
886+
* Default implementation will try to call {@link DeserializationProblemHandler#handleWeirdStringValue}
887+
* on configured handlers, if any, to allow for recovery; if recovery does not
888+
* succeed, will throw {@link InvalidFormatException} with given message.
889+
*
890+
* @param targetClass Type of property into which incoming number should be converted
891+
* @param value String value from which to deserialize property value
892+
* @param msg Error message template caller wants to use if exception is to be thrown
893+
* @param msgArgs Optional arguments to use for message, if any
894+
*
895+
* @return Property value to use
896+
*
897+
* @throws IOException To indicate unrecoverable problem, usually based on <code>msg</code>
898+
*
899+
* @since 2.8
900+
*/
901+
public Object handleWeirdStringValue(Class<?> targetClass, String value,
902+
String msg, Object... msgArgs)
903+
throws IOException
904+
{
905+
// but if not handled, just throw exception
906+
if (msgArgs.length > 0) {
907+
msg = String.format(msg, msgArgs);
908+
}
909+
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
910+
while (h != null) {
911+
// Can bail out if it's handled
912+
Object key = h.value().handleWeirdStringValue(this, targetClass, value, msg);
913+
if (key != DeserializationProblemHandler.NOT_HANDLED) {
914+
return key;
915+
}
916+
h = h.next();
917+
}
918+
throw weirdStringException(value, targetClass, msg);
919+
}
920+
881921
/**
882922
* Method that deserializers should call if they encounter a numeric value
883923
* that can not be converted to target property type, in cases where some
@@ -917,7 +957,7 @@ public Object handleWeirdNumberValue(Class<?> targetClass, Number value,
917957
}
918958
throw weirdNumberException(value, targetClass, msg);
919959
}
920-
960+
921961
/**
922962
* @since 2.8
923963
*/
@@ -997,19 +1037,6 @@ public void reportInstantiationException(Class<?> instClass,
9971037
throw instantiationException(instClass, msg);
9981038
}
9991039

1000-
/**
1001-
* @since 2.8
1002-
*/
1003-
public void reportWeirdStringException(String value, Class<?> instClass,
1004-
String msg, Object... msgArgs)
1005-
throws JsonMappingException
1006-
{
1007-
if (msgArgs.length > 0) {
1008-
msg = String.format(msg, msgArgs);
1009-
}
1010-
throw weirdStringException(value, instClass, msg);
1011-
}
1012-
10131040
/**
10141041
* @since 2.8
10151042
*/
@@ -1112,7 +1139,7 @@ public JsonMappingException mappingException(String msgTemplate, Object... args)
11121139
* key type.
11131140
* Note that most of the time this method should NOT be called; instead,
11141141
* {@link #handleWeirdKey} should be called which will call this method
1115-
* if necessary, but may also use overrides.
1142+
* if necessary.
11161143
*/
11171144
public JsonMappingException weirdKeyException(Class<?> keyClass, String keyValue,
11181145
String msg) {
@@ -1122,9 +1149,33 @@ public JsonMappingException weirdKeyException(Class<?> keyClass, String keyValue
11221149
keyValue, keyClass);
11231150
}
11241151

1152+
/**
1153+
* Helper method for constructing exception to indicate that input JSON
1154+
* String was not suitable for deserializing into given target type.
1155+
* Note that most of the time this method should NOT be called; instead,
1156+
* {@link #handleWeirdStringValue} should be called which will call this method
1157+
* if necessary.
1158+
*
1159+
* @param value String value from input being deserialized
1160+
* @param instClass Type that String should be deserialized into
1161+
* @param msg Message that describes specific problem
1162+
*
1163+
* @since 2.1
1164+
*/
1165+
public JsonMappingException weirdStringException(String value, Class<?> instClass,
1166+
String msg) {
1167+
return InvalidFormatException.from(_parser,
1168+
String.format("Can not deserialize value of type %s from String %s: %s",
1169+
instClass.getName(), _quotedString(value), msg),
1170+
value, instClass);
1171+
}
1172+
11251173
/**
11261174
* Helper method for constructing exception to indicate that input JSON
11271175
* Number was not suitable for deserializing into given target type.
1176+
* Note that most of the time this method should NOT be called; instead,
1177+
* {@link #handleWeirdNumberValue} should be called which will call this method
1178+
* if necessary.
11281179
*/
11291180
public JsonMappingException weirdNumberException(Number value, Class<?> instClass,
11301181
String msg) {
@@ -1135,9 +1186,12 @@ public JsonMappingException weirdNumberException(Number value, Class<?> instClas
11351186
}
11361187

11371188
/**
1138-
* Helper method for constructing exception to indicate that given JSON
1139-
* Object field name was not in format to be able to deserialize specified
1140-
* key type.
1189+
* Helper method for constructing exception to indicate that given type id
1190+
* could not be resolved to a valid subtype of specified base type, during
1191+
* polymorphic deserialization.
1192+
* Note that most of the time this method should NOT be called; instead,
1193+
* {@link #handleUnknownTypeId} should be called which will call this method
1194+
* if necessary.
11411195
*/
11421196
public JsonMappingException unknownTypeIdException(JavaType baseType, String typeId,
11431197
String extraDesc) {
@@ -1180,27 +1234,6 @@ public JsonMappingException instantiationException(Class<?> instClass, String ms
11801234
instClass.getName(), msg));
11811235
}
11821236

1183-
/**
1184-
* Method that will construct an exception suitable for throwing when
1185-
* some String values are acceptable, but the one encountered is not.
1186-
*
1187-
* @param value String value from input being deserialized
1188-
* @param instClass Type that String should be deserialized into
1189-
* @param msg Message that describes specific problem
1190-
*
1191-
* @since 2.1
1192-
*
1193-
* @deprecated Since 2.8 use {@link #reportWeirdStringException} instead
1194-
*/
1195-
@Deprecated
1196-
public JsonMappingException weirdStringException(String value, Class<?> instClass,
1197-
String msg) {
1198-
return InvalidFormatException.from(_parser,
1199-
String.format("Can not deserialize value of type %s from String %s: %s",
1200-
instClass.getName(), _quotedString(value), msg),
1201-
value, instClass);
1202-
}
1203-
12041237
/**
12051238
* Helper method for indicating that the current token was expected to be another
12061239
* token.

src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,41 @@ public Object handleWeirdKey(DeserializationContext ctxt,
105105
}
106106

107107
/**
108-
* Method called when a number value (integral or floating-point from input
108+
* Method called when a String value
109+
* can not be converted to a non-String value type due to specific problem
110+
* (as opposed to String values never being usable).
111+
* Handler may choose to do one of 3 things:
112+
*<ul>
113+
* <li>Indicate it does not know what to do by returning {@link #NOT_HANDLED}
114+
* </li>
115+
* <li>Throw a {@link IOException} to indicate specific fail message (instead of
116+
* standard exception caller would throw
117+
* </li>
118+
* <li>Return actual converted value (of type <code>targetType</code>) to use as
119+
* replacement, and continue processing.
120+
* </li>
121+
* </ul>
122+
*
123+
* @param failureMsg Message that will be used by caller (by calling
124+
* {@link DeserializationContext#weirdNumberException})
125+
* to indicate type of failure unless handler produces key to use
126+
*
127+
* @return Either {@link #NOT_HANDLED} to indicate that handler does not know
128+
* what to do (and exception may be thrown), or value to use as key (possibly
129+
* <code>null</code>
130+
*
131+
* @since 2.8
132+
*/
133+
public Object handleWeirdStringValue(DeserializationContext ctxt,
134+
Class<?> targetType, String valueToConvert,
135+
String failureMsg)
136+
throws IOException
137+
{
138+
return NOT_HANDLED;
139+
}
140+
141+
/**
142+
* Method called when a numeric value (integral or floating-point from input
109143
* can not be converted to a non-numeric value type due to specific problem
110144
* (as opposed to numeric values never being usable).
111145
* Handler may choose to do one of 3 things:
@@ -115,7 +149,7 @@ public Object handleWeirdKey(DeserializationContext ctxt,
115149
* <li>Throw a {@link IOException} to indicate specific fail message (instead of
116150
* standard exception caller would throw
117151
* </li>
118-
* <li>Return actual converted value (of type <code>targetType</code> to use as
152+
* <li>Return actual converted value (of type <code>targetType</code>) to use as
119153
* replacement, and continue processing.
120154
* </li>
121155
* </ul>

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,8 @@ protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
153153
try {
154154
return _customFormat.parse(str);
155155
} catch (ParseException e) {
156-
ctxt.reportWeirdStringException(str, handledType(),
156+
return (java.util.Date) ctxt.handleWeirdStringValue(handledType(), str,
157157
"expected format \"%s\"", _formatString);
158-
return null;
159158
}
160159
}
161160
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,8 @@ private final Object _deserializeAltString(JsonParser p, DeserializationContext
191191
return _enumDefaultValue;
192192
}
193193
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
194-
ctxt.reportWeirdStringException(name, _enumClass(),
194+
return ctxt.handleWeirdStringValue(_enumClass(), name,
195195
"value not one of declared Enum instance names: %s", lookup.keys());
196-
// fall-through if not immediately thrown
197196
}
198197
return null;
199198
}

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -122,35 +122,36 @@ public JsonDeserializer<Object> getContentDeserializer() {
122122
*/
123123

124124
@Override
125-
public EnumMap<?,?> deserialize(JsonParser jp, DeserializationContext ctxt)
125+
public EnumMap<?,?> deserialize(JsonParser p, DeserializationContext ctxt)
126126
throws IOException
127127
{
128128
// Ok: must point to START_OBJECT
129-
if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
130-
return _deserializeFromEmpty(jp, ctxt);
129+
if (p.getCurrentToken() != JsonToken.START_OBJECT) {
130+
return _deserializeFromEmpty(p, ctxt);
131131
}
132132
EnumMap result = constructMap();
133133
final JsonDeserializer<Object> valueDes = _valueDeserializer;
134134
final TypeDeserializer typeDeser = _valueTypeDeserializer;
135135

136-
while ((jp.nextToken()) == JsonToken.FIELD_NAME) {
137-
String keyName = jp.getCurrentName(); // just for error message
136+
while ((p.nextToken()) == JsonToken.FIELD_NAME) {
137+
String keyName = p.getCurrentName(); // just for error message
138138
// but we need to let key deserializer handle it separately, nonetheless
139139
Enum<?> key = (Enum<?>) _keyDeserializer.deserializeKey(keyName, ctxt);
140140
if (key == null) {
141141
if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
142-
ctxt.reportWeirdStringException(keyName, _enumClass, "value not one of declared Enum instance names for %s",
142+
return (EnumMap<?,?>) ctxt.handleWeirdStringValue(_enumClass, keyName,
143+
"value not one of declared Enum instance names for %s",
143144
_mapType.getKeyType());
144145
}
145146
/* 24-Mar-2012, tatu: Null won't work as a key anyway, so let's
146147
* just skip the entry then. But we must skip the value as well, if so.
147148
*/
148-
jp.nextToken();
149-
jp.skipChildren();
149+
p.nextToken();
150+
p.skipChildren();
150151
continue;
151152
}
152153
// And then the value...
153-
JsonToken t = jp.nextToken();
154+
JsonToken t = p.nextToken();
154155
/* note: MUST check for nulls separately: deserializers will
155156
* not handle them (and maybe fail or return bogus data)
156157
*/
@@ -160,9 +161,9 @@ public EnumMap<?,?> deserialize(JsonParser jp, DeserializationContext ctxt)
160161
if (t == JsonToken.VALUE_NULL) {
161162
value = valueDes.getNullValue(ctxt);
162163
} else if (typeDeser == null) {
163-
value = valueDes.deserialize(jp, ctxt);
164+
value = valueDes.deserialize(p, ctxt);
164165
} else {
165-
value = valueDes.deserializeWithType(jp, ctxt, typeDeser);
166+
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
166167
}
167168
} catch (Exception e) {
168169
wrapAndThrow(e, result, keyName);

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,8 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
476476
}
477477
return Long.valueOf(value);
478478
} catch (IllegalArgumentException iae) {
479-
ctxt.reportWeirdStringException(text, _valueClass, "not a valid number");
480-
return null;
479+
return ctxt.handleWeirdStringValue(_valueClass, text,
480+
"not a valid number");
481481
}
482482
case JsonTokenId.ID_START_ARRAY:
483483
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
@@ -573,8 +573,8 @@ public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws
573573
try {
574574
return new BigInteger(text);
575575
} catch (IllegalArgumentException iae) {
576-
ctxt.reportWeirdStringException(text, _valueClass, "not a valid representation");
577-
return null;
576+
return (BigInteger) ctxt.handleWeirdStringValue(_valueClass, text,
577+
"not a valid representation");
578578
}
579579
}
580580
// String is ok too, can easily convert; otherwise, no can do:
@@ -608,8 +608,8 @@ public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt)
608608
try {
609609
return new BigDecimal(text);
610610
} catch (IllegalArgumentException iae) {
611-
ctxt.reportWeirdStringException(text, _valueClass, "not a valid representation");
612-
return null;
611+
return (BigDecimal) ctxt.handleWeirdStringValue(_valueClass, text,
612+
"not a valid representation");
613613
}
614614
case JsonTokenId.ID_START_ARRAY:
615615
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {

0 commit comments

Comments
 (0)