diff options
author | Tatu Saloranta <tatu.saloranta@iki.fi> | 2016-05-21 18:08:41 -0700 |
---|---|---|
committer | Tatu Saloranta <tatu.saloranta@iki.fi> | 2016-05-21 18:08:41 -0700 |
commit | 68245442f201f8bcfbb075fecc66a6c06b236ea9 (patch) | |
tree | 53f79ec073cd9a0200390657ee1808c64153b49c /src/main/java/com/fasterxml/jackson/databind | |
parent | 1d1d603070bad721278fccff6e37c4efeb82a2b7 (diff) | |
download | jackson-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')
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(); |