Skip to content

Commit 6824544

Browse files
committed
Yet more work for #1207; added DeserializationProblemHandler.handleMissingInstantiator() (and supporting functionality), to replace instantiationException(Class, String)
1 parent 1d1d603 commit 6824544

File tree

7 files changed

+223
-86
lines changed

7 files changed

+223
-86
lines changed

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

Lines changed: 106 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,6 @@ public Object handleWeirdNumberValue(Class<?> targetClass, Number value,
954954
String msg, Object... msgArgs)
955955
throws IOException
956956
{
957-
// but if not handled, just throw exception
958957
if (msgArgs.length > 0) {
959958
msg = String.format(msg, msgArgs);
960959
}
@@ -977,46 +976,44 @@ public Object handleWeirdNumberValue(Class<?> targetClass, Number value,
977976
}
978977

979978
/**
980-
* Method that deserializers should call if they encounter a type id
981-
* (for polymorphic deserialization) that can not be resolved to an
982-
* actual type; usually since there is no mapping defined.
983-
* Default implementation will try to call {@link DeserializationProblemHandler#handleUnknownTypeId}
984-
* on configured handlers, if any, to allow for recovery; if recovery does not
985-
* succeed, will throw exception constructed with {@link #unknownTypeIdException}.
986-
*
987-
* @param baseType Base type from which resolution starts
988-
* @param id Type id that could not be converted
989-
* @param extraDesc Additional problem description to add to default exception message,
990-
* if resolution fails.
979+
* Method that deserializers should call if they fail to instantiate value
980+
* due to lack of viable instantiator (usually creator, that is, constructor
981+
* or static factory method). Method should be called at point where value
982+
* has not been decoded, so that handler has a chance to handle decoding
983+
* using alternate mechanism, and handle underlying content (possibly by
984+
* just skipping it) to keep input state valid
991985
*
992-
* @return {@link JavaType} that id resolves to
986+
* @param instClass Type that was to be instantiated
987+
* @param p Parser that points to the JSON value to decode
993988
*
994-
* @throws IOException To indicate unrecoverable problem, if resolution can not
995-
* be made to work
989+
* @return Object that should be constructed, if any; has to be of type <code>instClass</code>
996990
*
997991
* @since 2.8
998992
*/
999-
public JavaType handleUnknownTypeId(JavaType baseType, String id,
1000-
String extraDesc) throws IOException
993+
public Object handleMissingInstantiator(Class<?> instClass, JsonParser p,
994+
String msg, Object... msgArgs)
995+
throws IOException
1001996
{
997+
if (msgArgs.length > 0) {
998+
msg = String.format(msg, msgArgs);
999+
}
10021000
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
10031001
while (h != null) {
10041002
// Can bail out if it's handled
1005-
JavaType type = h.value().handleUnknownTypeId(this, baseType, id, extraDesc);
1006-
if (type != null) {
1007-
if (type.hasRawClass(Void.class)) {
1008-
return null;
1009-
}
1010-
// But ensure there's type compatibility
1011-
if (type.isTypeOrSubTypeOf(baseType.getRawClass())) {
1012-
return type;
1003+
Object instance = h.value().handleMissingInstantiator(this,
1004+
instClass, p, msg);
1005+
if (instance != DeserializationProblemHandler.NOT_HANDLED) {
1006+
// Sanity check for broken handlers, otherwise nasty to debug:
1007+
if ((instance == null) || instClass.isInstance(instance)) {
1008+
return instance;
10131009
}
1014-
throw unknownTypeIdException(baseType, id,
1015-
"problem handler tried to resolve into non-subtype: "+type);
1010+
throw instantiationException(instClass, String.format(
1011+
"DeserializationProblemHandler.handleMissingInstantiator() for type %s returned value of type %s",
1012+
instClass, instance.getClass()));
10161013
}
10171014
h = h.next();
10181015
}
1019-
throw unknownTypeIdException(baseType, id, extraDesc);
1016+
throw instantiationException(instClass, msg);
10201017
}
10211018

10221019
/**
@@ -1062,12 +1059,76 @@ public Object handleInstantiationProblem(Class<?> instClass, Object argument,
10621059
throw instantiationException(instClass, t);
10631060
}
10641061

1062+
/**
1063+
* Method that deserializers should call if they encounter a type id
1064+
* (for polymorphic deserialization) that can not be resolved to an
1065+
* actual type; usually since there is no mapping defined.
1066+
* Default implementation will try to call {@link DeserializationProblemHandler#handleUnknownTypeId}
1067+
* on configured handlers, if any, to allow for recovery; if recovery does not
1068+
* succeed, will throw exception constructed with {@link #unknownTypeIdException}.
1069+
*
1070+
* @param baseType Base type from which resolution starts
1071+
* @param id Type id that could not be converted
1072+
* @param extraDesc Additional problem description to add to default exception message,
1073+
* if resolution fails.
1074+
*
1075+
* @return {@link JavaType} that id resolves to
1076+
*
1077+
* @throws IOException To indicate unrecoverable problem, if resolution can not
1078+
* be made to work
1079+
*
1080+
* @since 2.8
1081+
*/
1082+
public JavaType handleUnknownTypeId(JavaType baseType, String id,
1083+
String extraDesc) throws IOException
1084+
{
1085+
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
1086+
while (h != null) {
1087+
// Can bail out if it's handled
1088+
JavaType type = h.value().handleUnknownTypeId(this, baseType, id, extraDesc);
1089+
if (type != null) {
1090+
if (type.hasRawClass(Void.class)) {
1091+
return null;
1092+
}
1093+
// But ensure there's type compatibility
1094+
if (type.isTypeOrSubTypeOf(baseType.getRawClass())) {
1095+
return type;
1096+
}
1097+
throw unknownTypeIdException(baseType, id,
1098+
"problem handler tried to resolve into non-subtype: "+type);
1099+
}
1100+
h = h.next();
1101+
}
1102+
throw unknownTypeIdException(baseType, id, extraDesc);
1103+
}
1104+
10651105
/*
10661106
/**********************************************************
1067-
/* Methods for problem reporting
1107+
/* Methods for problem reporting, in cases where recovery
1108+
/* is not considered possible
10681109
/**********************************************************
10691110
*/
10701111

1112+
/**
1113+
* Method for deserializers to call
1114+
* when the token encountered was of type different than what <b>should</b>
1115+
* be seen at that position, usually within a sequence of expected tokens.
1116+
* Note that this method will throw a {@link JsonMappingException} and no
1117+
* recovery is attempted (via {@link DeserializationProblemHandler}, as
1118+
* problem is considered to be difficult to recover from, in general.
1119+
*
1120+
* @since 2.8
1121+
*/
1122+
public void reportWrongTokenException(JsonParser p,
1123+
JsonToken expToken, String msg, Object... msgArgs)
1124+
throws JsonMappingException
1125+
{
1126+
if (msgArgs.length > 0) {
1127+
msg = String.format(msg, msgArgs);
1128+
}
1129+
throw wrongTokenException(p, expToken, msg);
1130+
}
1131+
10711132
/**
10721133
* Helper method for reporting a problem with unhandled unknown property.
10731134
*
@@ -1093,32 +1154,6 @@ public void reportUnknownProperty(Object instanceOrClass, String fieldName,
10931154
instanceOrClass, fieldName, propIds);
10941155
}
10951156

1096-
/**
1097-
* @since 2.8
1098-
*/
1099-
public void reportInstantiationException(Class<?> instClass,
1100-
String msg, Object... msgArgs)
1101-
throws JsonMappingException
1102-
{
1103-
if (msgArgs.length > 0) {
1104-
msg = String.format(msg, msgArgs);
1105-
}
1106-
throw instantiationException(instClass, msg);
1107-
}
1108-
1109-
/**
1110-
* @since 2.8
1111-
*/
1112-
public JsonMappingException reportWrongTokenException(JsonParser p,
1113-
JsonToken expToken, String msg, Object... msgArgs)
1114-
throws JsonMappingException
1115-
{
1116-
if (msgArgs.length > 0) {
1117-
msg = String.format(msg, msgArgs);
1118-
}
1119-
throw wrongTokenException(p, expToken, msg);
1120-
}
1121-
11221157
/**
11231158
* @since 2.8
11241159
*/
@@ -1203,7 +1238,7 @@ public JsonMappingException mappingException(String msgTemplate, Object... args)
12031238
*/
12041239

12051240
/**
1206-
* Helper method for constructing {@link JsonMappingException} to indicated
1241+
* Helper method for constructing {@link JsonMappingException} to indicate
12071242
* that the token encountered was of type different than what <b>should</b>
12081243
* be seen at that position, usually within a sequence of expected tokens.
12091244
* Note that most of the time this method should NOT be directly called;
@@ -1288,6 +1323,21 @@ public JsonMappingException instantiationException(Class<?> instClass, Throwable
12881323
instClass.getName(), t.getMessage()), t);
12891324
}
12901325

1326+
/**
1327+
* Helper method for constructing instantiation exception for specified type,
1328+
* to indicate that instantiation failed due to missing instantiator
1329+
* (creator; constructor or factory method).
1330+
*<p>
1331+
* Note that most of the time this method should NOT be called; instead,
1332+
* {@link #handleMissingInstantiator} should be called which will call this method
1333+
* if necessary.
1334+
*/
1335+
public JsonMappingException instantiationException(Class<?> instClass, String msg) {
1336+
return JsonMappingException.from(_parser,
1337+
String.format("Can not construct instance of %s: %s",
1338+
instClass.getName(), msg));
1339+
}
1340+
12911341
/**
12921342
* Helper method for constructing exception to indicate that given type id
12931343
* could not be resolved to a valid subtype of specified base type, during
@@ -1313,16 +1363,6 @@ public JsonMappingException unknownTypeIdException(JavaType baseType, String typ
13131363
/**********************************************************
13141364
*/
13151365

1316-
/**
1317-
* @deprecated Since 2.8 use {@link #reportInstantiationException} instead
1318-
*/
1319-
@Deprecated
1320-
public JsonMappingException instantiationException(Class<?> instClass, String msg) {
1321-
return JsonMappingException.from(_parser,
1322-
String.format("Can not construct instance of %s: %s",
1323-
instClass.getName(), msg));
1324-
}
1325-
13261366
/**
13271367
* @since 2.5
13281368
*

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,8 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
146146
public Object deserialize(JsonParser p, DeserializationContext ctxt)
147147
throws IOException
148148
{
149-
// This method should never be called...
150-
ctxt.reportInstantiationException(_baseType.getRawClass(),
149+
return ctxt.handleMissingInstantiator(_baseType.getRawClass(), p,
151150
"abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information");
152-
// 05-May-2016, tatu: Unlikely work as is but...
153-
return null;
154151
}
155152

156153
/*

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

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ public class BeanDeserializer
3030

3131
private static final long serialVersionUID = 1L;
3232

33+
/**
34+
* Lazily constructed exception used as root cause if reporting problem
35+
* with creator method that returns <code>null</code> (which is not allowed)
36+
*
37+
* @since 3.8
38+
*/
39+
protected transient Exception _nullFromCreator;
40+
3341
/*
3442
/**********************************************************
3543
/* Life-cycle, construction, initialization
@@ -397,10 +405,8 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
397405
bean = wrapInstantiationProblem(e, ctxt);
398406
}
399407
if (bean == null) {
400-
ctxt.reportInstantiationException(_beanType.getRawClass(),
401-
"JSON Creator returned null");
402-
// 05-May-2016, tatu: This won't really work well at all but...
403-
return null;
408+
return ctxt.handleInstantiationProblem(handledType(), null,
409+
_creatorReturnedNullException());
404410
}
405411
// [databind#631]: Assign current value, to be accessible by custom serializers
406412
p.setCurrentValue(bean);
@@ -906,4 +912,17 @@ protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p, D
906912
return wrapInstantiationProblem(e, ctxt);
907913
}
908914
}
915+
916+
/**
917+
* Helper method for getting a lazily construct exception to be reported
918+
* to {@link DeserializationContext#handleInstantiationProblem(Class, Object, Throwable)}.
919+
*
920+
* @since 2.8
921+
*/
922+
protected Exception _creatorReturnedNullException() {
923+
if (_nullFromCreator == null) {
924+
_nullFromCreator = new NullPointerException("JSON Creator returned null");
925+
}
926+
return _nullFromCreator;
927+
}
909928
}

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,11 +1186,13 @@ protected Object deserializeFromObjectUsingNonDefault(JsonParser p,
11861186
}
11871187
// should only occur for abstract types...
11881188
if (_beanType.isAbstract()) {
1189-
throw JsonMappingException.from(p, "Can not instantiate abstract type "+_beanType
1190-
+" (need to add/enable type information?)");
1189+
return ctxt.handleMissingInstantiator(_beanType.getRawClass(), p,
1190+
"Can not instantiate abstract type %s (need to add/enable type information?)",
1191+
_beanType);
11911192
}
1192-
throw JsonMappingException.from(p, "No suitable constructor found for type "
1193-
+_beanType+": can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)");
1193+
return ctxt.handleMissingInstantiator(_beanType.getRawClass(), p,
1194+
"No suitable constructor found for type %s: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)",
1195+
_beanType);
11941196
}
11951197

11961198
protected abstract Object _deserializeUsingPropertyBased(final JsonParser p,
@@ -1237,8 +1239,8 @@ public Object deserializeFromNumber(JsonParser p, DeserializationContext ctxt) t
12371239
}
12381240
return bean;
12391241
}
1240-
ctxt.reportInstantiationException(handledType(), "no suitable creator method found to deserialize from JSON integer number");
1241-
return null;
1242+
return ctxt.handleMissingInstantiator(handledType(), p,
1243+
"no suitable creator method found to deserialize from JSON integer number");
12421244
}
12431245

12441246
public Object deserializeFromString(JsonParser p, DeserializationContext ctxt) throws IOException
@@ -1287,9 +1289,8 @@ public Object deserializeFromDouble(JsonParser p, DeserializationContext ctxt) t
12871289
if (_delegateDeserializer != null) {
12881290
return _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(p, ctxt));
12891291
}
1290-
ctxt.reportInstantiationException(handledType(),
1292+
return ctxt.handleMissingInstantiator(handledType(), p,
12911293
"no suitable creator method found to deserialize from JSON floating-point number");
1292-
return null;
12931294
}
12941295

12951296
/**

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,39 @@ public Object handleInstantiationProblem(DeserializationContext ctxt,
206206
return NOT_HANDLED;
207207
}
208208

209+
/**
210+
* Method called when instance creation for a type fails due to lack of an
211+
* instantiator. Method is called before actual deserialization from input
212+
* is attempted, so handler may do one of following things:
213+
*<ul>
214+
* <li>Indicate it does not know what to do by returning {@link #NOT_HANDLED}
215+
* </li>
216+
* <li>Throw a {@link IOException} to indicate specific fail message (instead of
217+
* standard exception caller would throw
218+
* </li>
219+
* <li>Handle content to match (by consuming or skipping it), and return actual
220+
* instantiated value (of type <code>targetType</code>) to use as replacement;
221+
* value may be `null` as well as expected target type.
222+
* </li>
223+
* </ul>
224+
*
225+
* @param instClass Type that was to be instantiated
226+
* @param p Parser to use for accessing content that needs handling, to either
227+
* use it or skip it (latter with {@link JsonParser#skipChildren()}.
228+
*
229+
* @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
231+
* <code>null</code>
232+
*
233+
* @since 2.8
234+
*/
235+
public Object handleMissingInstantiator(DeserializationContext ctxt,
236+
Class<?> instClass, JsonParser p, String msg)
237+
throws IOException
238+
{
239+
return NOT_HANDLED;
240+
}
241+
209242
/**
210243
* Handler method called if resolution of type id from given String failed
211244
* to produce a subtype; usually because logical id is not mapped to actual

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,8 @@ public Map<Object,Object> deserialize(JsonParser p, DeserializationContext ctxt)
334334
_delegateDeserializer.deserialize(p, ctxt));
335335
}
336336
if (!_hasDefaultCreator) {
337-
ctxt.reportInstantiationException(getMapClass(), "No default constructor found");
338-
// 05-May-2016, tatu: Unlikely to really work...
339-
return null;
337+
return (Map<Object,Object> ) ctxt.handleMissingInstantiator(getMapClass(), p,
338+
"No default constructor found");
340339
}
341340
// Ok: must point to START_OBJECT, FIELD_NAME or END_OBJECT
342341
JsonToken t = p.getCurrentToken();

0 commit comments

Comments
 (0)