aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/fasterxml/jackson/databind
diff options
context:
space:
mode:
authorTatu Saloranta <tatu.saloranta@iki.fi>2016-05-21 18:08:41 -0700
committerTatu Saloranta <tatu.saloranta@iki.fi>2016-05-21 18:08:41 -0700
commit68245442f201f8bcfbb075fecc66a6c06b236ea9 (patch)
tree53f79ec073cd9a0200390657ee1808c64153b49c /src/main/java/com/fasterxml/jackson/databind
parent1d1d603070bad721278fccff6e37c4efeb82a2b7 (diff)
downloadjackson-databind-68245442f201f8bcfbb075fecc66a6c06b236ea9.tar.gz
Yet more work for #1207; added `DeserializationProblemHandler.handleMissingInstantiator()` (and supporting functionality), to replace `instantiationException(Class, String)`
Diffstat (limited to 'src/main/java/com/fasterxml/jackson/databind')
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java172
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java5
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java27
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java17
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java33
-rw-r--r--src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java5
6 files changed, 174 insertions, 85 deletions
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
index aee4e615a..113c7db4f 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java
@@ -954,7 +954,6 @@ public abstract class DeserializationContext
String msg, Object... msgArgs)
throws IOException
{
- // but if not handled, just throw exception
if (msgArgs.length > 0) {
msg = String.format(msg, msgArgs);
}
@@ -977,46 +976,44 @@ public abstract class DeserializationContext
}
/**
- * Method that deserializers should call if they encounter a type id
- * (for polymorphic deserialization) that can not be resolved to an
- * actual type; usually since there is no mapping defined.
- * Default implementation will try to call {@link DeserializationProblemHandler#handleUnknownTypeId}
- * on configured handlers, if any, to allow for recovery; if recovery does not
- * succeed, will throw exception constructed with {@link #unknownTypeIdException}.
- *
- * @param baseType Base type from which resolution starts
- * @param id Type id that could not be converted
- * @param extraDesc Additional problem description to add to default exception message,
- * if resolution fails.
+ * Method that deserializers should call if they fail to instantiate value
+ * due to lack of viable instantiator (usually creator, that is, constructor
+ * or static factory method). Method should be called at point where value
+ * has not been decoded, so that handler has a chance to handle decoding
+ * using alternate mechanism, and handle underlying content (possibly by
+ * just skipping it) to keep input state valid
*
- * @return {@link JavaType} that id resolves to
+ * @param instClass Type that was to be instantiated
+ * @param p Parser that points to the JSON value to decode
*
- * @throws IOException To indicate unrecoverable problem, if resolution can not
- * be made to work
+ * @return Object that should be constructed, if any; has to be of type <code>instClass</code>
*
* @since 2.8
*/
- public JavaType handleUnknownTypeId(JavaType baseType, String id,
- String extraDesc) throws IOException
+ public Object handleMissingInstantiator(Class<?> instClass, JsonParser p,
+ String msg, Object... msgArgs)
+ throws IOException
{
+ if (msgArgs.length > 0) {
+ msg = String.format(msg, msgArgs);
+ }
LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
while (h != null) {
// Can bail out if it's handled
- JavaType type = h.value().handleUnknownTypeId(this, baseType, id, extraDesc);
- if (type != null) {
- if (type.hasRawClass(Void.class)) {
- return null;
- }
- // But ensure there's type compatibility
- if (type.isTypeOrSubTypeOf(baseType.getRawClass())) {
- return type;
+ Object instance = h.value().handleMissingInstantiator(this,
+ instClass, p, msg);
+ if (instance != DeserializationProblemHandler.NOT_HANDLED) {
+ // Sanity check for broken handlers, otherwise nasty to debug:
+ if ((instance == null) || instClass.isInstance(instance)) {
+ return instance;
}
- throw unknownTypeIdException(baseType, id,
- "problem handler tried to resolve into non-subtype: "+type);
+ throw instantiationException(instClass, String.format(
+ "DeserializationProblemHandler.handleMissingInstantiator() for type %s returned value of type %s",
+ instClass, instance.getClass()));
}
h = h.next();
}
- throw unknownTypeIdException(baseType, id, extraDesc);
+ throw instantiationException(instClass, msg);
}
/**
@@ -1062,13 +1059,77 @@ public abstract class DeserializationContext
throw instantiationException(instClass, t);
}
+ /**
+ * Method that deserializers should call if they encounter a type id
+ * (for polymorphic deserialization) that can not be resolved to an
+ * actual type; usually since there is no mapping defined.
+ * Default implementation will try to call {@link DeserializationProblemHandler#handleUnknownTypeId}
+ * on configured handlers, if any, to allow for recovery; if recovery does not
+ * succeed, will throw exception constructed with {@link #unknownTypeIdException}.
+ *
+ * @param baseType Base type from which resolution starts
+ * @param id Type id that could not be converted
+ * @param extraDesc Additional problem description to add to default exception message,
+ * if resolution fails.
+ *
+ * @return {@link JavaType} that id resolves to
+ *
+ * @throws IOException To indicate unrecoverable problem, if resolution can not
+ * be made to work
+ *
+ * @since 2.8
+ */
+ public JavaType handleUnknownTypeId(JavaType baseType, String id,
+ String extraDesc) throws IOException
+ {
+ LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers();
+ while (h != null) {
+ // Can bail out if it's handled
+ JavaType type = h.value().handleUnknownTypeId(this, baseType, id, extraDesc);
+ if (type != null) {
+ if (type.hasRawClass(Void.class)) {
+ return null;
+ }
+ // But ensure there's type compatibility
+ if (type.isTypeOrSubTypeOf(baseType.getRawClass())) {
+ return type;
+ }
+ throw unknownTypeIdException(baseType, id,
+ "problem handler tried to resolve into non-subtype: "+type);
+ }
+ h = h.next();
+ }
+ throw unknownTypeIdException(baseType, id, extraDesc);
+ }
+
/*
/**********************************************************
- /* Methods for problem reporting
+ /* Methods for problem reporting, in cases where recovery
+ /* is not considered possible
/**********************************************************
*/
/**
+ * Method for deserializers to call
+ * when the token encountered was of type different than what <b>should</b>
+ * be seen at that position, usually within a sequence of expected tokens.
+ * Note that this method will throw a {@link JsonMappingException} and no
+ * recovery is attempted (via {@link DeserializationProblemHandler}, as
+ * problem is considered to be difficult to recover from, in general.
+ *
+ * @since 2.8
+ */
+ public void reportWrongTokenException(JsonParser p,
+ JsonToken expToken, String msg, Object... msgArgs)
+ throws JsonMappingException
+ {
+ if (msgArgs.length > 0) {
+ msg = String.format(msg, msgArgs);
+ }
+ throw wrongTokenException(p, expToken, msg);
+ }
+
+ /**
* Helper method for reporting a problem with unhandled unknown property.
*
* @param instanceOrClass Either value being populated (if one has been
@@ -1096,32 +1157,6 @@ public abstract class DeserializationContext
/**
* @since 2.8
*/
- public void reportInstantiationException(Class<?> instClass,
- String msg, Object... msgArgs)
- throws JsonMappingException
- {
- if (msgArgs.length > 0) {
- msg = String.format(msg, msgArgs);
- }
- throw instantiationException(instClass, msg);
- }
-
- /**
- * @since 2.8
- */
- public JsonMappingException reportWrongTokenException(JsonParser p,
- JsonToken expToken, String msg, Object... msgArgs)
- throws JsonMappingException
- {
- if (msgArgs.length > 0) {
- msg = String.format(msg, msgArgs);
- }
- throw wrongTokenException(p, expToken, msg);
- }
-
- /**
- * @since 2.8
- */
public void reportMappingException(String msg, Object... msgArgs)
throws JsonMappingException
{
@@ -1203,7 +1238,7 @@ public abstract class DeserializationContext
*/
/**
- * Helper method for constructing {@link JsonMappingException} to indicated
+ * Helper method for constructing {@link JsonMappingException} to indicate
* that the token encountered was of type different than what <b>should</b>
* be seen at that position, usually within a sequence of expected tokens.
* Note that most of the time this method should NOT be directly called;
@@ -1289,6 +1324,21 @@ public abstract class DeserializationContext
}
/**
+ * Helper method for constructing instantiation exception for specified type,
+ * to indicate that instantiation failed due to missing instantiator
+ * (creator; constructor or factory method).
+ *<p>
+ * Note that most of the time this method should NOT be called; instead,
+ * {@link #handleMissingInstantiator} should be called which will call this method
+ * if necessary.
+ */
+ public JsonMappingException instantiationException(Class<?> instClass, String msg) {
+ return JsonMappingException.from(_parser,
+ String.format("Can not construct instance of %s: %s",
+ instClass.getName(), msg));
+ }
+
+ /**
* Helper method for constructing exception to indicate that given type id
* could not be resolved to a valid subtype of specified base type, during
* polymorphic deserialization.
@@ -1314,16 +1364,6 @@ public abstract class DeserializationContext
*/
/**
- * @deprecated Since 2.8 use {@link #reportInstantiationException} instead
- */
- @Deprecated
- public JsonMappingException instantiationException(Class<?> instClass, String msg) {
- return JsonMappingException.from(_parser,
- String.format("Can not construct instance of %s: %s",
- instClass.getName(), msg));
- }
-
- /**
* @since 2.5
*
* @deprecated Since 2.8 use {@link #handleUnknownTypeId} instead
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java
index 54c92e3e9..36b7cf1a8 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java
@@ -146,11 +146,8 @@ public class AbstractDeserializer
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
- // This method should never be called...
- ctxt.reportInstantiationException(_baseType.getRawClass(),
+ return ctxt.handleMissingInstantiator(_baseType.getRawClass(), p,
"abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information");
- // 05-May-2016, tatu: Unlikely work as is but...
- return null;
}
/*
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
index 44fbaff5a..c77138740 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
@@ -30,6 +30,14 @@ public class BeanDeserializer
private static final long serialVersionUID = 1L;
+ /**
+ * Lazily constructed exception used as root cause if reporting problem
+ * with creator method that returns <code>null</code> (which is not allowed)
+ *
+ * @since 3.8
+ */
+ protected transient Exception _nullFromCreator;
+
/*
/**********************************************************
/* Life-cycle, construction, initialization
@@ -397,10 +405,8 @@ public class BeanDeserializer
bean = wrapInstantiationProblem(e, ctxt);
}
if (bean == null) {
- ctxt.reportInstantiationException(_beanType.getRawClass(),
- "JSON Creator returned null");
- // 05-May-2016, tatu: This won't really work well at all but...
- return null;
+ return ctxt.handleInstantiationProblem(handledType(), null,
+ _creatorReturnedNullException());
}
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
@@ -906,4 +912,17 @@ public class BeanDeserializer
return wrapInstantiationProblem(e, ctxt);
}
}
+
+ /**
+ * Helper method for getting a lazily construct exception to be reported
+ * to {@link DeserializationContext#handleInstantiationProblem(Class, Object, Throwable)}.
+ *
+ * @since 2.8
+ */
+ protected Exception _creatorReturnedNullException() {
+ if (_nullFromCreator == null) {
+ _nullFromCreator = new NullPointerException("JSON Creator returned null");
+ }
+ return _nullFromCreator;
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java
index 2187d58c9..3f49defe6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java
@@ -1186,11 +1186,13 @@ public abstract class BeanDeserializerBase
}
// should only occur for abstract types...
if (_beanType.isAbstract()) {
- throw JsonMappingException.from(p, "Can not instantiate abstract type "+_beanType
- +" (need to add/enable type information?)");
+ return ctxt.handleMissingInstantiator(_beanType.getRawClass(), p,
+ "Can not instantiate abstract type %s (need to add/enable type information?)",
+ _beanType);
}
- throw JsonMappingException.from(p, "No suitable constructor found for type "
- +_beanType+": can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)");
+ return ctxt.handleMissingInstantiator(_beanType.getRawClass(), p,
+ "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?)",
+ _beanType);
}
protected abstract Object _deserializeUsingPropertyBased(final JsonParser p,
@@ -1237,8 +1239,8 @@ public abstract class BeanDeserializerBase
}
return bean;
}
- ctxt.reportInstantiationException(handledType(), "no suitable creator method found to deserialize from JSON integer number");
- return null;
+ return ctxt.handleMissingInstantiator(handledType(), p,
+ "no suitable creator method found to deserialize from JSON integer number");
}
public Object deserializeFromString(JsonParser p, DeserializationContext ctxt) throws IOException
@@ -1287,9 +1289,8 @@ public abstract class BeanDeserializerBase
if (_delegateDeserializer != null) {
return _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(p, ctxt));
}
- ctxt.reportInstantiationException(handledType(),
+ return ctxt.handleMissingInstantiator(handledType(), p,
"no suitable creator method found to deserialize from JSON floating-point number");
- return null;
}
/**
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java
index 3069cce2e..a1de632d1 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializationProblemHandler.java
@@ -207,6 +207,39 @@ public abstract class DeserializationProblemHandler
}
/**
+ * Method called when instance creation for a type fails due to lack of an
+ * instantiator. Method is called before actual deserialization from input
+ * is attempted, so handler may do one of following things:
+ *<ul>
+ * <li>Indicate it does not know what to do by returning {@link #NOT_HANDLED}
+ * </li>
+ * <li>Throw a {@link IOException} to indicate specific fail message (instead of
+ * standard exception caller would throw
+ * </li>
+ * <li>Handle content to match (by consuming or skipping it), and return actual
+ * instantiated value (of type <code>targetType</code>) to use as replacement;
+ * value may be `null` as well as expected target type.
+ * </li>
+ * </ul>
+ *
+ * @param instClass Type that was to be instantiated
+ * @param p Parser to use for accessing content that needs handling, to either
+ * use it or skip it (latter with {@link JsonParser#skipChildren()}.
+ *
+ * @return Either {@link #NOT_HANDLED} to indicate that handler does not know
+ * what to do (and exception may be thrown), or value to use as key (possibly
+ * <code>null</code>
+ *
+ * @since 2.8
+ */
+ public Object handleMissingInstantiator(DeserializationContext ctxt,
+ Class<?> instClass, JsonParser p, String msg)
+ throws IOException
+ {
+ return NOT_HANDLED;
+ }
+
+ /**
* Handler method called if resolution of type id from given String failed
* to produce a subtype; usually because logical id is not mapped to actual
* implementation class.
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java
index 7509ed0cf..c110ad188 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java
@@ -334,9 +334,8 @@ public class MapDeserializer
_delegateDeserializer.deserialize(p, ctxt));
}
if (!_hasDefaultCreator) {
- ctxt.reportInstantiationException(getMapClass(), "No default constructor found");
- // 05-May-2016, tatu: Unlikely to really work...
- return null;
+ return (Map<Object,Object> ) ctxt.handleMissingInstantiator(getMapClass(), p,
+ "No default constructor found");
}
// Ok: must point to START_OBJECT, FIELD_NAME or END_OBJECT
JsonToken t = p.getCurrentToken();