Skip to content

Commit 851092c

Browse files
committed
More for #1207: now add DeserializationContext.handleUnexpectedToken() (and handler method in DeserializationProblemHandler), for the common failure mode of getting JSON token of unexpected type
1 parent 6824544 commit 851092c

26 files changed

+303
-304
lines changed

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

Lines changed: 96 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ public <T> T readValue(JsonParser p, Class<T> type) throws IOException {
770770
public <T> T readValue(JsonParser p, JavaType type) throws IOException {
771771
JsonDeserializer<Object> deser = findRootValueDeserializer(type);
772772
if (deser == null) {
773-
throw mappingException("Could not find JsonDeserializer for type %s", type);
773+
throw mappingException("Could not find JsonDeserializer for type "+type);
774774
}
775775
return (T) deser.deserialize(p, this);
776776
}
@@ -795,8 +795,9 @@ public <T> T readPropertyValue(JsonParser p, BeanProperty prop, JavaType type) t
795795
JsonDeserializer<Object> deser = findContextualValueDeserializer(type, prop);
796796
if (deser == null) {
797797
String propName = (prop == null) ? "NULL" : ("'"+prop.getName()+"'");
798-
throw mappingException("Could not find JsonDeserializer for type %s (via property %s)",
799-
type, propName);
798+
throw mappingException(String.format(
799+
"Could not find JsonDeserializer for type %s (via property %s)",
800+
type, propName));
800801
}
801802
return (T) deser.deserialize(p, this);
802803
}
@@ -1059,6 +1060,71 @@ public Object handleInstantiationProblem(Class<?> instClass, Object argument,
10591060
throw instantiationException(instClass, t);
10601061
}
10611062

1063+
/**
1064+
* Method that deserializers should call if the first token of the value to
1065+
* deserialize is of unexpected type (that is, type of token that deserializer
1066+
* can not handle). This could occur, for example, if a Number deserializer
1067+
* encounter {@link JsonToken#START_ARRAY} instead of
1068+
* {@link JsonToken#VALUE_NUMBER_INT} or {@link JsonToken#VALUE_NUMBER_FLOAT}.
1069+
*
1070+
* @param instClass Type that was to be instantiated
1071+
* @param p Parser that points to the JSON value to decode
1072+
*
1073+
* @return Object that should be constructed, if any; has to be of type <code>instClass</code>
1074+
*
1075+
* @since 2.8
1076+
*/
1077+
public Object handleUnexpectedToken(Class<?> instClass, JsonParser p)
1078+
throws IOException
1079+
{
1080+
return handleUnexpectedToken(instClass, p.getCurrentToken(), p, null);
1081+
}
1082+
1083+
/**
1084+
* Method that deserializers should call if the first token of the value to
1085+
* deserialize is of unexpected type (that is, type of token that deserializer
1086+
* can not handle). This could occur, for example, if a Number deserializer
1087+
* encounter {@link JsonToken#START_ARRAY} instead of
1088+
* {@link JsonToken#VALUE_NUMBER_INT} or {@link JsonToken#VALUE_NUMBER_FLOAT}.
1089+
*
1090+
* @param instClass Type that was to be instantiated
1091+
* @param p Parser that points to the JSON value to decode
1092+
*
1093+
* @return Object that should be constructed, if any; has to be of type <code>instClass</code>
1094+
*
1095+
* @since 2.8
1096+
*/
1097+
public Object handleUnexpectedToken(Class<?> instClass, JsonToken t,
1098+
JsonParser p,
1099+
String msg, Object... msgArgs)
1100+
throws IOException
1101+
{
1102+
if (msgArgs.length > 0) {
1103+
msg = String.format(msg, msgArgs);
1104+
}
1105+
/*
1106+
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
1107+
while (h != null) {
1108+
Object instance = h.value().handleUnexpectedToken(this,
1109+
instClass, t, p, msg);
1110+
if (instance != DeserializationProblemHandler.NOT_HANDLED) {
1111+
if ((instance == null) || instClass.isInstance(instance)) {
1112+
return instance;
1113+
}
1114+
throw mappingException(String.format(
1115+
"DeserializationProblemHandler.handleUnexpectedToken() for type %s returned value of type %s",
1116+
instClass, instance.getClass()));
1117+
}
1118+
h = h.next();
1119+
}
1120+
*/
1121+
if (msg == null) {
1122+
msg = String.format("Can not deserialize instance of %s out of %s token",
1123+
_calcName(instClass), t);
1124+
}
1125+
throw mappingException(msg);
1126+
}
1127+
10621128
/**
10631129
* Method that deserializers should call if they encounter a type id
10641130
* (for polymorphic deserialization) that can not be resolved to an
@@ -1123,7 +1189,7 @@ public void reportWrongTokenException(JsonParser p,
11231189
JsonToken expToken, String msg, Object... msgArgs)
11241190
throws JsonMappingException
11251191
{
1126-
if (msgArgs.length > 0) {
1192+
if ((msg != null) && (msgArgs.length > 0)) {
11271193
msg = String.format(msg, msgArgs);
11281194
}
11291195
throw wrongTokenException(p, expToken, msg);
@@ -1166,50 +1232,18 @@ public void reportMappingException(String msg, Object... msgArgs)
11661232
throw mappingException(msg);
11671233
}
11681234

1169-
/**
1170-
* @since 2.8
1171-
*/
1172-
public void reportMappingException(Class<?> targetClass, JsonToken t) throws JsonMappingException {
1173-
throw mappingException(targetClass, t);
1174-
}
1175-
1176-
/**
1177-
* @since 2.8
1178-
*/
1179-
public void reportMappingException(Class<?> targetClass) throws JsonMappingException {
1180-
throw mappingException(targetClass);
1181-
}
1182-
11831235
/*
11841236
/**********************************************************
11851237
/* Methods for constructing exceptions, "untyped"
11861238
/**********************************************************
11871239
*/
1188-
1189-
/**
1190-
* Helper method for constructing generic mapping exception for specified type
1191-
*
1192-
* @deprecated Since 2.8 use {@link #reportMappingException(Class)} instead
1193-
*/
1194-
@Deprecated
1195-
public JsonMappingException mappingException(Class<?> targetClass) {
1196-
return mappingException(targetClass, _parser.getCurrentToken());
1197-
}
1198-
1199-
/**
1200-
* @deprecated Since 2.8 use {@link #reportMappingException(Class, JsonToken)} instead
1201-
*/
1202-
@Deprecated
1203-
public JsonMappingException mappingException(Class<?> targetClass, JsonToken token) {
1204-
return JsonMappingException.from(_parser,
1205-
String.format("Can not deserialize instance of %s out of %s token",
1206-
_calcName(targetClass), token));
1207-
}
12081240

12091241
/**
1210-
* @deprecated Since 2.8 use {@link #reportMappingException(String, Object...)} instead
1242+
* Helper method for constructing generic mapping exception with specified
1243+
* message and current location information
1244+
*
1245+
* @since 2.6
12111246
*/
1212-
@Deprecated
12131247
public JsonMappingException mappingException(String message) {
12141248
return JsonMappingException.from(getParser(), message);
12151249
}
@@ -1219,17 +1253,34 @@ public JsonMappingException mappingException(String message) {
12191253
* message and current location information
12201254
*
12211255
* @since 2.6
1222-
*
1223-
* @deprecated Since 2.8 use {@link #reportMappingException(String, Object...)} instead
12241256
*/
1225-
@Deprecated
12261257
public JsonMappingException mappingException(String msgTemplate, Object... args) {
12271258
if (args != null && args.length > 0) {
12281259
msgTemplate = String.format(msgTemplate, args);
12291260
}
12301261
return JsonMappingException.from(getParser(), msgTemplate);
12311262
}
12321263

1264+
/**
1265+
* Helper method for constructing generic mapping exception for specified type
1266+
*
1267+
* @deprecated Since 2.8 use {@link #handleUnexpectedToken(Class, JsonParser)} instead
1268+
*/
1269+
@Deprecated
1270+
public JsonMappingException mappingException(Class<?> targetClass) {
1271+
return mappingException(targetClass, _parser.getCurrentToken());
1272+
}
1273+
1274+
/**
1275+
* @deprecated Since 2.8 use {@link #handleUnexpectedToken(Class, JsonParser)} instead
1276+
*/
1277+
@Deprecated
1278+
public JsonMappingException mappingException(Class<?> targetClass, JsonToken token) {
1279+
return JsonMappingException.from(_parser,
1280+
String.format("Can not deserialize instance of %s out of %s token",
1281+
_calcName(targetClass), token));
1282+
}
1283+
12331284
/*
12341285
/**********************************************************
12351286
/* Methods for constructing semantic exceptions; usually not
@@ -1359,7 +1410,7 @@ public JsonMappingException unknownTypeIdException(JavaType baseType, String typ
13591410

13601411
/*
13611412
/**********************************************************
1362-
/* Methods for constructing semantic exceptions
1413+
/* Deprecated exception factory methods
13631414
/**********************************************************
13641415
*/
13651416

@@ -1393,7 +1444,7 @@ public JsonMappingException endOfInputException(Class<?> instClass) {
13931444

13941445
/*
13951446
/**********************************************************
1396-
/* Overridable internal methods
1447+
/* Other internal methods
13971448
/**********************************************************
13981449
*/
13991450

@@ -1415,12 +1466,6 @@ protected DateFormat getDateFormat()
14151466
protected String determineClassName(Object instance) {
14161467
return ClassUtil.getClassDescription(instance);
14171468
}
1418-
1419-
/*
1420-
/**********************************************************
1421-
/* Other internal methods
1422-
/**********************************************************
1423-
*/
14241469

14251470
protected String _calcName(Class<?> cls) {
14261471
if (cls.isArray()) {

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,7 @@ protected final Object _deserializeOther(JsonParser p, DeserializationContext ct
185185
return deserializeFromObject(p, ctxt);
186186
default:
187187
}
188-
ctxt.reportMappingException(handledType());
189-
return null;
188+
return ctxt.handleUnexpectedToken(handledType(), p);
190189
}
191190

192191
@Deprecated // since 2.8; remove unless getting used
@@ -514,8 +513,7 @@ protected Object deserializeFromNull(JsonParser p, DeserializationContext ctxt)
514513
p2.close();
515514
return ob;
516515
}
517-
ctxt.reportMappingException(handledType());
518-
return null;
516+
return ctxt.handleUnexpectedToken(handledType(), p);
519517
}
520518

521519
/*

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,11 +1353,10 @@ public Object deserializeFromArray(JsonParser p, DeserializationContext ctxt) th
13531353
if (t == JsonToken.END_ARRAY) {
13541354
return null;
13551355
}
1356-
ctxt.reportMappingException(handledType(), JsonToken.START_ARRAY);
1357-
return null;
1356+
return ctxt.handleUnexpectedToken(handledType(),
1357+
JsonToken.START_ARRAY, p, null);
13581358
}
1359-
ctxt.reportMappingException(handledType());
1360-
return null;
1359+
return ctxt.handleUnexpectedToken(handledType(), p);
13611360
}
13621361

13631362
public Object deserializeFromEmbedded(JsonParser p, DeserializationContext ctxt)

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

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ protected final Object finishBuild(DeserializationContext ctxt, Object builder)
143143
*/
144144
@Override
145145
public final Object deserialize(JsonParser p, DeserializationContext ctxt)
146-
throws IOException, JsonProcessingException
146+
throws IOException
147147
{
148148
JsonToken t = p.getCurrentToken();
149149

@@ -157,28 +157,29 @@ public final Object deserialize(JsonParser p, DeserializationContext ctxt)
157157
return finishBuild(ctxt, builder);
158158
}
159159
// and then others, generally requiring use of @JsonCreator
160-
switch (t) {
161-
case VALUE_STRING:
162-
return finishBuild(ctxt, deserializeFromString(p, ctxt));
163-
case VALUE_NUMBER_INT:
164-
return finishBuild(ctxt, deserializeFromNumber(p, ctxt));
165-
case VALUE_NUMBER_FLOAT:
166-
return finishBuild(ctxt, deserializeFromDouble(p, ctxt));
167-
case VALUE_EMBEDDED_OBJECT:
168-
return p.getEmbeddedObject();
169-
case VALUE_TRUE:
170-
case VALUE_FALSE:
171-
return finishBuild(ctxt, deserializeFromBoolean(p, ctxt));
172-
case START_ARRAY:
173-
// these only work if there's a (delegating) creator...
174-
return finishBuild(ctxt, deserializeFromArray(p, ctxt));
175-
case FIELD_NAME:
176-
case END_OBJECT:
177-
return finishBuild(ctxt, deserializeFromObject(p, ctxt));
178-
default:
179-
ctxt.reportMappingException(handledType());
180-
return null;
160+
if (t != null) {
161+
switch (t) {
162+
case VALUE_STRING:
163+
return finishBuild(ctxt, deserializeFromString(p, ctxt));
164+
case VALUE_NUMBER_INT:
165+
return finishBuild(ctxt, deserializeFromNumber(p, ctxt));
166+
case VALUE_NUMBER_FLOAT:
167+
return finishBuild(ctxt, deserializeFromDouble(p, ctxt));
168+
case VALUE_EMBEDDED_OBJECT:
169+
return p.getEmbeddedObject();
170+
case VALUE_TRUE:
171+
case VALUE_FALSE:
172+
return finishBuild(ctxt, deserializeFromBoolean(p, ctxt));
173+
case START_ARRAY:
174+
// these only work if there's a (delegating) creator...
175+
return finishBuild(ctxt, deserializeFromArray(p, ctxt));
176+
case FIELD_NAME:
177+
case END_OBJECT:
178+
return finishBuild(ctxt, deserializeFromObject(p, ctxt));
179+
default:
180+
}
181181
}
182+
return ctxt.handleUnexpectedToken(handledType(), p);
182183
}
183184

184185
/**
@@ -189,7 +190,7 @@ public final Object deserialize(JsonParser p, DeserializationContext ctxt)
189190
@Override
190191
public Object deserialize(JsonParser p, DeserializationContext ctxt,
191192
Object builder)
192-
throws IOException, JsonProcessingException
193+
throws IOException
193194
{
194195
/* Important: we call separate method which does NOT call
195196
* 'finishBuild()', to avoid problems with recursion

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.IOException;
44

55
import com.fasterxml.jackson.core.JsonParser;
6+
import com.fasterxml.jackson.core.JsonToken;
67
import com.fasterxml.jackson.databind.DeserializationConfig;
78
import com.fasterxml.jackson.databind.DeserializationContext;
89
import com.fasterxml.jackson.databind.JavaType;
@@ -172,6 +173,41 @@ public Object handleWeirdNumberValue(DeserializationContext ctxt,
172173
return NOT_HANDLED;
173174
}
174175

176+
/**
177+
* Method that deserializers should call if the first token of the value to
178+
* deserialize is of unexpected type (that is, type of token that deserializer
179+
* can not handle). This could occur, for example, if a Number deserializer
180+
* encounter {@link JsonToken#START_ARRAY} instead of
181+
* {@link JsonToken#VALUE_NUMBER_INT} or {@link JsonToken#VALUE_NUMBER_FLOAT}.
182+
*<ul>
183+
* <li>Indicate it does not know what to do by returning {@link #NOT_HANDLED}
184+
* </li>
185+
* <li>Throw a {@link IOException} to indicate specific fail message (instead of
186+
* standard exception caller would throw
187+
* </li>
188+
* <li>Handle content to match (by consuming or skipping it), and return actual
189+
* instantiated value (of type <code>targetType</code>) to use as replacement;
190+
* value may be `null` as well as expected target type.
191+
* </li>
192+
* </ul>
193+
*
194+
* @param failureMsg Message that will be used by caller
195+
* to indicate type of failure unless handler produces value to use
196+
*
197+
* @return Either {@link #NOT_HANDLED} to indicate that handler does not know
198+
* what to do (and exception may be thrown), or value to use (possibly
199+
* <code>null</code>
200+
*
201+
* @since 2.8
202+
*/
203+
public Object handleUnexpectedToken(DeserializationContext ctxt,
204+
Class<?> targetType, JsonToken t, JsonParser p,
205+
String failureMsg)
206+
throws IOException
207+
{
208+
return NOT_HANDLED;
209+
}
210+
175211
/**
176212
* Method called when instance creation for a type fails due to an exception.
177213
* Handler may choose to do one of following things:
@@ -194,7 +230,7 @@ public Object handleWeirdNumberValue(DeserializationContext ctxt,
194230
* @param t Exception that caused instantiation failure
195231
*
196232
* @return Either {@link #NOT_HANDLED} to indicate that handler does not know
197-
* what to do (and exception may be thrown), or value to use as key (possibly
233+
* what to do (and exception may be thrown), or value to use (possibly
198234
* <code>null</code>
199235
*
200236
* @since 2.8
@@ -227,7 +263,7 @@ public Object handleInstantiationProblem(DeserializationContext ctxt,
227263
* use it or skip it (latter with {@link JsonParser#skipChildren()}.
228264
*
229265
* @return Either {@link #NOT_HANDLED} to indicate that handler does not know
230-
* what to do (and exception may be thrown), or value to use as key (possibly
266+
* what to do (and exception may be thrown), or value to use (possibly
231267
* <code>null</code>
232268
*
233269
* @since 2.8

0 commit comments

Comments
 (0)