diff --git a/release-notes/VERSION b/release-notes/VERSION index 1b70c41f6d..c071fcf870 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -8,6 +8,8 @@ Project: jackson-databind #47: Support `@JsonValue` for (Map) key serialization #113: Problem deserializing polymorphic types with @JsonCreator +#165: Add `DeserializationContext.getContextualType()` to let deserializer + known the expected type. #408: External type id does not allow use of 'visible=true' #421: @JsonCreator not used in case of multiple creators with parameter names (reported by Lovro P, lpandzic@github) diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java index 9b9e069bdd..e6b2ccfdab 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java @@ -128,6 +128,15 @@ public abstract class DeserializationContext * @since 2.3 */ protected transient ContextAttributes _attributes; + + /** + * Type of {@link JsonDeserializer} (or, more specifically, + * {@link ContextualizableDeserializer}) that is being + * contextualized currently. + * + * @since 2.5 + */ + protected LinkedNode _currentType; /* /********************************************************** @@ -224,7 +233,7 @@ public final TypeFactory getTypeFactory() { /* /********************************************************** - /* Generic attributes (2.3+) + /* Access to per-call state, like generic attributes (2.3+) /********************************************************** */ @@ -239,10 +248,27 @@ public DeserializationContext setAttribute(Object key, Object value) _attributes = _attributes.withPerCallAttribute(key, value); return this; } - + + /** + * Accessor to {@link JavaType} of currently contextualized + * {@link ContextualDeserializer}, if any. + * This is sometimes useful for generic {@link JsonDeserializer}s that + * do not get passed (or do not retain) type information when being + * constructed: happens for example for deserializers constructed + * from annotations. + * + * @since 2.5 + * + * @return Type of {@link ContextualDeserializer} being contextualized, + * if process is on-going; null if not. + */ + public JavaType getContextualType() { + return (_currentType == null) ? null : _currentType.value(); + } + /* /********************************************************** - /* Public API, accessors + /* Public API, config setting accessors /********************************************************** */ @@ -379,7 +405,7 @@ public final JsonDeserializer findContextualValueDeserializer(JavaType t { JsonDeserializer deser = _cache.findValueDeserializer(this, _factory, type); if (deser != null) { - deser = (JsonDeserializer) handleSecondaryContextualization(deser, prop); + deser = (JsonDeserializer) handleSecondaryContextualization(deser, prop, type); } return deser; } @@ -391,8 +417,8 @@ public final JsonDeserializer findContextualValueDeserializer(JavaType t * {@link #findRootValueDeserializer(JavaType)}. * This method is usually called from within {@link ResolvableDeserializer#resolve}, * and expectation is that caller then calls either - * {@link #handlePrimaryContextualization(JsonDeserializer, BeanProperty)} or - * {@link #handleSecondaryContextualization(JsonDeserializer, BeanProperty)} at a + * {@link #handlePrimaryContextualization(JsonDeserializer, BeanProperty, JavaType)} or + * {@link #handleSecondaryContextualization(JsonDeserializer, BeanProperty, JavaType)} at a * later point, as necessary. * * @since 2.5 @@ -415,7 +441,7 @@ public final JsonDeserializer findRootValueDeserializer(JavaType type) if (deser == null) { // can this occur? return null; } - deser = (JsonDeserializer) handleSecondaryContextualization(deser, null); + deser = (JsonDeserializer) handleSecondaryContextualization(deser, null, type); TypeDeserializer typeDeser = _factory.findTypeDeserializer(_config, type); if (typeDeser != null) { // important: contextualize to indicate this is for root value @@ -576,18 +602,31 @@ public abstract KeyDeserializer keyDeserializerInstance(Annotated annotated, * * @param prop Property for which the given primary deserializer is used; never null. * - * @since 2.3 + * @since 2.5 */ public JsonDeserializer handlePrimaryContextualization(JsonDeserializer deser, - BeanProperty prop) + BeanProperty prop, JavaType type) throws JsonMappingException { if (deser instanceof ContextualDeserializer) { - deser = ((ContextualDeserializer) deser).createContextual(this, prop); + _currentType = new LinkedNode(type, _currentType); + try { + deser = ((ContextualDeserializer) deser).createContextual(this, prop); + } finally { + _currentType = _currentType.next(); + } } return deser; } + @Deprecated // since 2.5; remove from 2.6 + public JsonDeserializer handlePrimaryContextualization(JsonDeserializer deser, + BeanProperty prop) + throws JsonMappingException + { + return handlePrimaryContextualization(deser, prop, TypeFactory.unknownType()); + } + /** * Method called for secondary property deserializers (ones * NOT directly created to deal with an annotatable POJO property, @@ -602,8 +641,24 @@ public JsonDeserializer handlePrimaryContextualization(JsonDeserializer de * @param prop Property for which deserializer is used, if any; null * when deserializing root values * - * @since 2.3 + * @since 2.5 */ + public JsonDeserializer handleSecondaryContextualization(JsonDeserializer deser, + BeanProperty prop, JavaType type) + throws JsonMappingException + { + if (deser instanceof ContextualDeserializer) { + _currentType = new LinkedNode(type, _currentType); + try { + deser = ((ContextualDeserializer) deser).createContextual(this, prop); + } finally { + _currentType = _currentType.next(); + } + } + return deser; + } + + @Deprecated // since 2.5; remove from 2.6 public JsonDeserializer handleSecondaryContextualization(JsonDeserializer deser, BeanProperty prop) throws JsonMappingException @@ -613,7 +668,7 @@ public JsonDeserializer handleSecondaryContextualization(JsonDeserializer } return deser; } - + /* /********************************************************** /* Parsing methods that may use reusable/-cyclable objects diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index d3b774d867..6b654d1b94 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -797,7 +797,7 @@ protected CreatorProperty constructCreatorProperty(DeserializationContext ctxt, metadata); if (deser != null) { // As per [Issue#462] need to ensure we contextualize deserializer before passing it on - deser = ctxt.handlePrimaryContextualization(deser, prop); + deser = ctxt.handlePrimaryContextualization(deser, prop, type); prop = prop.withValueDeserializer(deser); } return prop; 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 102e28e385..6ae4c9ad4d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -441,7 +441,8 @@ public void resolve(DeserializationContext ctxt) /* Important! This is the only place where actually handle "primary" * property deserializers -- call is different from other places. */ - JsonDeserializer cd = ctxt.handlePrimaryContextualization(deser, prop); + JsonDeserializer cd = ctxt.handlePrimaryContextualization(deser, prop, + prop.getType()); if (cd != deser) { prop = prop.withValueDeserializer(cd); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java index 7aaa60100f..478436f5f4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/CollectionDeserializer.java @@ -154,11 +154,11 @@ public CollectionDeserializer createContextual(DeserializationContext ctxt, JsonDeserializer valueDeser = _valueDeserializer; // #125: May have a content converter valueDeser = findConvertingContentDeserializer(ctxt, property, valueDeser); + final JavaType vt = _collectionType.getContentType(); if (valueDeser == null) { - valueDeser = ctxt.findContextualValueDeserializer( - _collectionType.getContentType(), property); + valueDeser = ctxt.findContextualValueDeserializer(vt, property); } else { // if directly assigned, probably not yet contextual, so: - valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property); + valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt); } // and finally, type deserializer needs context as well TypeDeserializer valueTypeDeser = _valueTypeDeserializer; diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/DelegatingDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/DelegatingDeserializer.java index 59ec3213d3..bb91ef11ca 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/DelegatingDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/DelegatingDeserializer.java @@ -67,7 +67,9 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { - JsonDeserializer del = ctxt.handleSecondaryContextualization(_delegatee, property); + JavaType vt = ctxt.constructType(_delegatee.handledType()); + JsonDeserializer del = ctxt.handleSecondaryContextualization(_delegatee, + property, vt); if (del == _delegatee) { return this; } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java index d25eeb4ce2..ef1740a63c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumMapDeserializer.java @@ -74,10 +74,11 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, BeanPro kd = ctxt.findKeyDeserializer(_mapType.getKeyType(), property); } JsonDeserializer vd = _valueDeserializer; + final JavaType vt = _mapType.getContentType(); if (vd == null) { - vd = ctxt.findContextualValueDeserializer(_mapType.getContentType(), property); + vd = ctxt.findContextualValueDeserializer(vt, property); } else { // if directly assigned, probably not yet contextual, so: - vd = ctxt.handleSecondaryContextualization(vd, property); + vd = ctxt.handleSecondaryContextualization(vd, property, vt); } TypeDeserializer vtd = _valueTypeDeserializer; if (vtd != null) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java index 3c7aae5546..bae969f03c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumSetDeserializer.java @@ -64,7 +64,7 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, if (deser == null) { deser = ctxt.findContextualValueDeserializer(_enumType, property); } else { // if directly assigned, probably not yet contextual, so: - deser = ctxt.handleSecondaryContextualization(deser, property); + deser = ctxt.handleSecondaryContextualization(deser, property, _enumType); } return withDeserializer(deser); } 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 90a6ae9cd1..9e6c46cb01 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 @@ -236,10 +236,11 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, JsonDeserializer vd = _valueDeserializer; // #125: May have a content converter vd = findConvertingContentDeserializer(ctxt, property, vd); + final JavaType vt = _mapType.getContentType(); if (vd == null) { - vd = ctxt.findContextualValueDeserializer(_mapType.getContentType(), property); + vd = ctxt.findContextualValueDeserializer(vt, property); } else { // if directly assigned, probably not yet contextual, so: - vd = ctxt.handleSecondaryContextualization(vd, property); + vd = ctxt.handleSecondaryContextualization(vd, property, vt); } TypeDeserializer vtd = _valueTypeDeserializer; if (vtd != null) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java index 99b9d9c7ca..e9c7949413 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java @@ -132,10 +132,11 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, } JsonDeserializer vd = _valueDeserializer; vd = findConvertingContentDeserializer(ctxt, property, vd); + JavaType contentType = _type.containedType(1); if (vd == null) { - vd = ctxt.findContextualValueDeserializer(_type.containedType(1), property); + vd = ctxt.findContextualValueDeserializer(contentType, property); } else { // if directly assigned, probably not yet contextual, so: - vd = ctxt.handleSecondaryContextualization(vd, property); + vd = ctxt.handleSecondaryContextualization(vd, property, contentType); } TypeDeserializer vtd = _valueTypeDeserializer; if (vtd != null) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java index d7ff64e167..7916f6816b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/ObjectArrayDeserializer.java @@ -89,10 +89,11 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, JsonDeserializer deser = _elementDeserializer; // #125: May have a content converter deser = findConvertingContentDeserializer(ctxt, property, deser); + final JavaType vt = _arrayType.getContentType(); if (deser == null) { - deser = ctxt.findContextualValueDeserializer(_arrayType.getContentType(), property); + deser = ctxt.findContextualValueDeserializer(vt, property); } else { // if directly assigned, probably not yet contextual, so: - deser = ctxt.handleSecondaryContextualization(deser, property); + deser = ctxt.handleSecondaryContextualization(deser, property, vt); } TypeDeserializer elemTypeDeser = _elementTypeDeserializer; if (elemTypeDeser != null) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDelegatingDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDelegatingDeserializer.java index da4e048e6c..1f7a28aece 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDelegatingDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDelegatingDeserializer.java @@ -119,7 +119,8 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, BeanPro { // First: if already got serializer to delegate to, contextualize it: if (_delegateDeserializer != null) { - JsonDeserializer deser = ctxt.handleSecondaryContextualization(_delegateDeserializer, property); + JsonDeserializer deser = ctxt.handleSecondaryContextualization(_delegateDeserializer, + property, _delegateType); if (deser != _delegateDeserializer) { return withDelegate(_converter, _delegateType, deser); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringArrayDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringArrayDeserializer.java index 142eda3559..9244bb7636 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringArrayDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringArrayDeserializer.java @@ -144,10 +144,11 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, BeanPro JsonDeserializer deser = _elementDeserializer; // #125: May have a content converter deser = findConvertingContentDeserializer(ctxt, property, deser); + JavaType type = ctxt.constructType(String.class); if (deser == null) { - deser = ctxt.findContextualValueDeserializer(ctxt.constructType(String.class), property); + deser = ctxt.findContextualValueDeserializer(type, property); } else { // if directly assigned, probably not yet contextual, so: - deser = ctxt.handleSecondaryContextualization(deser, property); + deser = ctxt.handleSecondaryContextualization(deser, property, type); } // Ok ok: if all we got is the default String deserializer, can just forget about it if (deser != null && this.isDefaultDeserializer(deser)) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java index e6a5a0dca2..38bd8a4235 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringCollectionDeserializer.java @@ -106,15 +106,16 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, } } JsonDeserializer valueDeser = _valueDeserializer; + final JavaType valueType = _collectionType.getContentType(); if (valueDeser == null) { // #125: May have a content converter valueDeser = findConvertingContentDeserializer(ctxt, property, valueDeser); if (valueDeser == null) { // And we may also need to get deserializer for String - valueDeser = ctxt.findContextualValueDeserializer( _collectionType.getContentType(), property); + valueDeser = ctxt.findContextualValueDeserializer(valueType, property); } } else { // if directly assigned, probably not yet contextual, so: - valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property); + valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, valueType); } if (isDefaultDeserializer(valueDeser)) { valueDeser = null; diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java index 505aa1fcac..7333f16684 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/UntypedObjectDeserializer.java @@ -109,10 +109,11 @@ public void resolve(DeserializationContext ctxt) throws JsonMappingException // and then do bogus contextualization, in case custom ones need to resolve dependencies of // their own - _mapDeserializer = (JsonDeserializer) ctxt.handleSecondaryContextualization(_mapDeserializer, null); - _listDeserializer = (JsonDeserializer) ctxt.handleSecondaryContextualization(_listDeserializer, null); - _stringDeserializer = (JsonDeserializer) ctxt.handleSecondaryContextualization(_stringDeserializer, null); - _numberDeserializer = (JsonDeserializer) ctxt.handleSecondaryContextualization(_numberDeserializer, null); + JavaType unknown = TypeFactory.unknownType(); + _mapDeserializer = (JsonDeserializer) ctxt.handleSecondaryContextualization(_mapDeserializer, null, unknown); + _listDeserializer = (JsonDeserializer) ctxt.handleSecondaryContextualization(_listDeserializer, null, unknown); + _stringDeserializer = (JsonDeserializer) ctxt.handleSecondaryContextualization(_stringDeserializer, null, unknown); + _numberDeserializer = (JsonDeserializer) ctxt.handleSecondaryContextualization(_numberDeserializer, null, unknown); } @SuppressWarnings("unchecked") diff --git a/src/test/java/com/fasterxml/jackson/databind/contextual/TestContextualDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/contextual/TestContextualDeserialization.java index 9d3a4d7d85..19ab6df333 100644 --- a/src/test/java/com/fasterxml/jackson/databind/contextual/TestContextualDeserialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/contextual/TestContextualDeserialization.java @@ -8,11 +8,12 @@ import java.util.*; import com.fasterxml.jackson.annotation.*; - import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; /** @@ -20,17 +21,9 @@ * that can use contextual information (like field/method * annotations) for configuration. */ +@SuppressWarnings("serial") public class TestContextualDeserialization extends BaseMapTest { - /* - /********************************************************** - /* Helper classes - /********************************************************** - */ - - /* NOTE: important; MUST be considered a 'Jackson' annotation to be seen - * (or recognized otherwise via AnnotationIntrospect.isHandled()) - */ @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotation @@ -153,6 +146,34 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, return new MyContextualDeserializer(propertyName); } } + + static class GenericStringDeserializer + extends StdScalarDeserializer + implements ContextualDeserializer + { + final String _value; + + public GenericStringDeserializer() { this("N/A"); } + protected GenericStringDeserializer(String value) { + super(String.class); + _value = value; + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) { + return new GenericStringDeserializer(String.valueOf(ctxt.getContextualType().getRawClass().getSimpleName())); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) { + return _value; + } + } + + static class GenericBean { + @JsonDeserialize(contentUsing=GenericStringDeserializer.class) + public Map stuff; + } /* /********************************************************** @@ -259,7 +280,16 @@ public void testAnnotatedMap() throws Exception assertEquals("1", entry.getKey()); assertEquals("map=2", entry.getValue().value); } - + + // for [databind#165] + public void testContextualType() throws Exception { + GenericBean bean = new ObjectMapper().readValue(aposToQuotes("{'stuff':{'1':'b'}}"), + GenericBean.class); + assertNotNull(bean.stuff); + assertEquals(1, bean.stuff.size()); + assertEquals("String", bean.stuff.get(Integer.valueOf(1))); + } + /* /********************************************************** /* Helper methods