From 10f44221fa0e8ffbfbc39384581b190f5f0cc106 Mon Sep 17 00:00:00 2001 From: Cowtowncoder Date: Mon, 29 Sep 2014 18:20:17 -0700 Subject: [PATCH] Fix #565: add support for `Map.Entry` --- release-notes/VERSION | 1 + .../deser/BasicDeserializerFactory.java | 286 +++++++++++------- .../deser/BeanDeserializerFactory.java | 64 +--- .../deser/std/MapEntryDeserializer.java | 246 +++++++++++++++ .../deser/TestMapDeserialization.java | 32 +- .../databind/ser/TestMapSerialization.java | 4 + 6 files changed, 469 insertions(+), 164 deletions(-) create mode 100644 src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java diff --git a/release-notes/VERSION b/release-notes/VERSION index 8e830e8896..08a32db43c 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -17,6 +17,7 @@ Version: 2.5.0 (xx-xxx-2014) #550: Minor optimization: prune introspection of "well-known" JDK types #552: Improved handling for ISO-8601 (date) format (contributed by Jerome G, geronimo-iia@github) +#565: Add support for handling `Map.Entry` ------------------------------------------------------------------------ === History: === 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 9a284f1d02..efbb895b41 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -3,6 +3,7 @@ import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.databind.*; @@ -10,6 +11,7 @@ import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; import com.fasterxml.jackson.databind.deser.impl.CreatorCollector; import com.fasterxml.jackson.databind.deser.std.*; +import com.fasterxml.jackson.databind.ext.OptionalHandlerFactory; import com.fasterxml.jackson.databind.introspect.*; import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; @@ -39,6 +41,7 @@ public abstract class BasicDeserializerFactory private final static Class CLASS_STRING = String.class; private final static Class CLASS_CHAR_BUFFER = CharSequence.class; private final static Class CLASS_ITERABLE = Iterable.class; + private final static Class CLASS_MAP_ENTRY = Map.Entry.class; /** * We need a placeholder for creator properties that don't have name @@ -832,7 +835,7 @@ public JsonDeserializer createArrayDeserializer(DeserializationContext ctxt, // Very first thing: is deserializer hard-coded for elements? JsonDeserializer contentDeser = elemType.getValueHandler(); - // Then optional type info (1.5): if type has been resolved, we may already know type deserializer: + // Then optional type info: if type has been resolved, we may already know type deserializer: TypeDeserializer elemTypeDeser = elemType.getTypeHandler(); // but if not, may still be possible to find: if (elemTypeDeser == null) { @@ -861,21 +864,6 @@ public JsonDeserializer createArrayDeserializer(DeserializationContext ctxt, return deser; } - protected JsonDeserializer _findCustomArrayDeserializer(ArrayType type, - DeserializationConfig config, BeanDescription beanDesc, - TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) - throws JsonMappingException - { - for (Deserializers d : _factoryConfig.deserializers()) { - JsonDeserializer deser = d.findArrayDeserializer(type, config, - beanDesc, elementTypeDeserializer, elementDeserializer); - if (deser != null) { - return deser; - } - } - return null; - } - /* /********************************************************** /* JsonDeserializerFactory impl: Collection(-like) deserializers @@ -971,21 +959,6 @@ protected CollectionType _mapAbstractCollectionType(JavaType type, Deserializati } return (CollectionType) config.constructSpecializedType(type, collectionClass); } - - protected JsonDeserializer _findCustomCollectionDeserializer(CollectionType type, - DeserializationConfig config, BeanDescription beanDesc, - TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) - throws JsonMappingException - { - for (Deserializers d : _factoryConfig.deserializers()) { - JsonDeserializer deser = d.findCollectionDeserializer(type, config, beanDesc, - elementTypeDeserializer, elementDeserializer); - if (deser != null) { - return deser; - } - } - return null; - } // Copied almost verbatim from "createCollectionDeserializer" -- should try to share more code @Override @@ -1017,27 +990,12 @@ public JsonDeserializer createCollectionLikeDeserializer(DeserializationConte return deser; } - protected JsonDeserializer _findCustomCollectionLikeDeserializer(CollectionLikeType type, - DeserializationConfig config, BeanDescription beanDesc, - TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) - throws JsonMappingException - { - for (Deserializers d : _factoryConfig.deserializers()) { - JsonDeserializer deser = d.findCollectionLikeDeserializer(type, config, beanDesc, - elementTypeDeserializer, elementDeserializer); - if (deser != null) { - return deser; - } - } - return null; - } - /* /********************************************************** /* JsonDeserializerFactory impl: Map(-like) deserializers /********************************************************** */ - + @Override public JsonDeserializer createMapDeserializer(DeserializationContext ctxt, MapType type, BeanDescription beanDesc) @@ -1160,38 +1118,6 @@ public JsonDeserializer createMapLikeDeserializer(DeserializationContext ctxt return deser; } - protected JsonDeserializer _findCustomMapDeserializer(MapType type, - DeserializationConfig config, BeanDescription beanDesc, - KeyDeserializer keyDeserializer, - TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) - throws JsonMappingException - { - for (Deserializers d : _factoryConfig.deserializers()) { - JsonDeserializer deser = d.findMapDeserializer(type, config, beanDesc, - keyDeserializer, elementTypeDeserializer, elementDeserializer); - if (deser != null) { - return deser; - } - } - return null; - } - - protected JsonDeserializer _findCustomMapLikeDeserializer(MapLikeType type, - DeserializationConfig config, BeanDescription beanDesc, - KeyDeserializer keyDeserializer, - TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) - throws JsonMappingException - { - for (Deserializers d : _factoryConfig.deserializers()) { - JsonDeserializer deser = d.findMapLikeDeserializer(type, config, beanDesc, - keyDeserializer, elementTypeDeserializer, elementDeserializer); - if (deser != null) { - return deser; - } - } - return null; - } - /* /********************************************************** /* JsonDeserializerFactory impl: Enum deserializers @@ -1241,19 +1167,6 @@ public JsonDeserializer createEnumDeserializer(DeserializationContext ctxt, } return deser; } - - protected JsonDeserializer _findCustomEnumDeserializer(Class type, - DeserializationConfig config, BeanDescription beanDesc) - throws JsonMappingException - { - for (Deserializers d : _factoryConfig.deserializers()) { - JsonDeserializer deser = d.findEnumDeserializer(type, config, beanDesc); - if (deser != null) { - return deser; - } - } - return null; - } /* /********************************************************** @@ -1276,19 +1189,6 @@ public JsonDeserializer createTreeDeserializer(DeserializationConfig config, } return JsonNodeDeserializer.getDeserializer(nodeClass); } - - protected JsonDeserializer _findCustomTreeNodeDeserializer(Class type, - DeserializationConfig config, BeanDescription beanDesc) - throws JsonMappingException - { - for (Deserializers d : _factoryConfig.deserializers()) { - JsonDeserializer deser = d.findTreeNodeDeserializer(type, config, beanDesc); - if (deser != null) { - return deser; - } - } - return null; - } /* /********************************************************** @@ -1329,6 +1229,18 @@ public TypeDeserializer findTypeDeserializer(DeserializationConfig config, return b.buildTypeDeserializer(config, baseType, subtypes); } + /** + * Overridable method called after checking all other types. + * + * @since 2.2 + */ + protected JsonDeserializer findOptionalStdDeserializer(DeserializationContext ctxt, + JavaType type, BeanDescription beanDesc) + throws JsonMappingException + { + return OptionalHandlerFactory.instance.findDeserializer(type, ctxt.getConfig(), beanDesc); + } + /* /********************************************************** /* JsonDeserializerFactory impl (partial): key deserializers @@ -1499,11 +1411,31 @@ public JsonDeserializer findDefaultDeserializer(DeserializationContext ctxt, if (rawType == CLASS_ITERABLE) { // [Issue#199]: Can and should 'upgrade' to a Collection type: TypeFactory tf = ctxt.getTypeFactory(); - JavaType elemType = (type.containedTypeCount() > 0) ? type.containedType(0) : TypeFactory.unknownType(); + JavaType[] tps = tf.findTypeParameters(type, CLASS_ITERABLE); + JavaType elemType = (tps == null || tps.length != 1) ? TypeFactory.unknownType() : tps[0]; CollectionType ct = tf.constructCollectionType(Collection.class, elemType); // Should we re-introspect beanDesc? For now let's not... return createCollectionDeserializer(ctxt, ct, beanDesc); } + if (rawType == CLASS_MAP_ENTRY) { + final DeserializationConfig config = ctxt.getConfig(); + TypeFactory tf = ctxt.getTypeFactory(); + JavaType[] tps = tf.findTypeParameters(type, CLASS_MAP_ENTRY); + JavaType kt, vt; + if (tps == null || tps.length != 2) { + kt = vt = TypeFactory.unknownType(); + } else { + kt = tps[0]; + vt = tps[1]; + } + TypeDeserializer vts = (TypeDeserializer) vt.getTypeHandler(); + if (vts == null) { + vts = findTypeDeserializer(config, vt); + } + JsonDeserializer valueDeser = vt.getValueHandler(); + KeyDeserializer keyDes = (KeyDeserializer) kt.getValueHandler(); + return new MapEntryDeserializer(type, keyDes, valueDeser, vts); + } String clsName = rawType.getName(); if (rawType.isPrimitive() || clsName.startsWith("java.")) { // Primitives/wrappers, other Numbers: @@ -1519,9 +1451,151 @@ public JsonDeserializer findDefaultDeserializer(DeserializationContext ctxt, if (rawType == TokenBuffer.class) { return new TokenBufferDeserializer(); } + if (AtomicReference.class.isAssignableFrom(rawType)) { + // Must find parameterization + TypeFactory tf = ctxt.getTypeFactory(); + JavaType[] params = tf.findTypeParameters(type, AtomicReference.class); + JavaType referencedType; + if (params == null || params.length < 1) { // untyped (raw) + referencedType = TypeFactory.unknownType(); + } else { + referencedType = params[0]; + } + TypeDeserializer vts = findTypeDeserializer(ctxt.getConfig(), referencedType); + BeanDescription refdDesc = ctxt.getConfig().introspectClassAnnotations(referencedType); + JsonDeserializer deser = findDeserializerFromAnnotation(ctxt, refdDesc.getClassInfo()); + return new AtomicReferenceDeserializer(referencedType, vts, deser); + } + JsonDeserializer deser = findOptionalStdDeserializer(ctxt, type, beanDesc); + if (deser != null) { + return deser; + } return JdkDeserializers.find(rawType, clsName); } + /* + /********************************************************** + /* Helper methods, finding custom deserializers + /********************************************************** + */ + + protected JsonDeserializer _findCustomArrayDeserializer(ArrayType type, + DeserializationConfig config, BeanDescription beanDesc, + TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) + throws JsonMappingException + { + for (Deserializers d : _factoryConfig.deserializers()) { + JsonDeserializer deser = d.findArrayDeserializer(type, config, + beanDesc, elementTypeDeserializer, elementDeserializer); + if (deser != null) { + return deser; + } + } + return null; + } + + @SuppressWarnings("unchecked") + protected JsonDeserializer _findCustomBeanDeserializer(JavaType type, + DeserializationConfig config, BeanDescription beanDesc) + throws JsonMappingException + { + for (Deserializers d : _factoryConfig.deserializers()) { + JsonDeserializer deser = d.findBeanDeserializer(type, config, beanDesc); + if (deser != null) { + return (JsonDeserializer) deser; + } + } + return null; + } + + protected JsonDeserializer _findCustomCollectionDeserializer(CollectionType type, + DeserializationConfig config, BeanDescription beanDesc, + TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) + throws JsonMappingException + { + for (Deserializers d : _factoryConfig.deserializers()) { + JsonDeserializer deser = d.findCollectionDeserializer(type, config, beanDesc, + elementTypeDeserializer, elementDeserializer); + if (deser != null) { + return deser; + } + } + return null; + } + + protected JsonDeserializer _findCustomCollectionLikeDeserializer(CollectionLikeType type, + DeserializationConfig config, BeanDescription beanDesc, + TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) + throws JsonMappingException + { + for (Deserializers d : _factoryConfig.deserializers()) { + JsonDeserializer deser = d.findCollectionLikeDeserializer(type, config, beanDesc, + elementTypeDeserializer, elementDeserializer); + if (deser != null) { + return deser; + } + } + return null; + } + + protected JsonDeserializer _findCustomEnumDeserializer(Class type, + DeserializationConfig config, BeanDescription beanDesc) + throws JsonMappingException + { + for (Deserializers d : _factoryConfig.deserializers()) { + JsonDeserializer deser = d.findEnumDeserializer(type, config, beanDesc); + if (deser != null) { + return deser; + } + } + return null; + } + + protected JsonDeserializer _findCustomMapDeserializer(MapType type, + DeserializationConfig config, BeanDescription beanDesc, + KeyDeserializer keyDeserializer, + TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) + throws JsonMappingException + { + for (Deserializers d : _factoryConfig.deserializers()) { + JsonDeserializer deser = d.findMapDeserializer(type, config, beanDesc, + keyDeserializer, elementTypeDeserializer, elementDeserializer); + if (deser != null) { + return deser; + } + } + return null; + } + + protected JsonDeserializer _findCustomMapLikeDeserializer(MapLikeType type, + DeserializationConfig config, BeanDescription beanDesc, + KeyDeserializer keyDeserializer, + TypeDeserializer elementTypeDeserializer, JsonDeserializer elementDeserializer) + throws JsonMappingException + { + for (Deserializers d : _factoryConfig.deserializers()) { + JsonDeserializer deser = d.findMapLikeDeserializer(type, config, beanDesc, + keyDeserializer, elementTypeDeserializer, elementDeserializer); + if (deser != null) { + return deser; + } + } + return null; + } + + protected JsonDeserializer _findCustomTreeNodeDeserializer(Class type, + DeserializationConfig config, BeanDescription beanDesc) + throws JsonMappingException + { + for (Deserializers d : _factoryConfig.deserializers()) { + JsonDeserializer deser = d.findTreeNodeDeserializer(type, config, beanDesc); + if (deser != null) { + return deser; + } + } + return null; + } + /* /********************************************************** /* Helper methods, value/content/key type introspection diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index c61c677901..2c20c1e4da 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -2,21 +2,18 @@ import java.lang.reflect.Type; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.annotation.ObjectIdGenerator; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.annotation.ObjectIdResolver; + import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig; import com.fasterxml.jackson.databind.deser.impl.*; -import com.fasterxml.jackson.databind.deser.std.AtomicReferenceDeserializer; import com.fasterxml.jackson.databind.deser.std.ThrowableDeserializer; -import com.fasterxml.jackson.databind.ext.OptionalHandlerFactory; import com.fasterxml.jackson.databind.introspect.*; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; -import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.util.ArrayBuilders; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.SimpleBeanPropertyDefinition; @@ -87,28 +84,6 @@ public DeserializerFactory withConfig(DeserializerFactoryConfig config) return new BeanDeserializerFactory(config); } - /* - /********************************************************** - /* Overrides for super-class methods used for finding - /* custom deserializers - /********************************************************** - */ - - // Note: NOT overriding, superclass has no matching method - @SuppressWarnings("unchecked") - protected JsonDeserializer _findCustomBeanDeserializer(JavaType type, - DeserializationConfig config, BeanDescription beanDesc) - throws JsonMappingException - { - for (Deserializers d : _factoryConfig.deserializers()) { - JsonDeserializer deser = d.findBeanDeserializer(type, config, beanDesc); - if (deser != null) { - return (JsonDeserializer) deser; - } - } - return null; - } - /* /********************************************************** /* DeserializerFactory API implementation @@ -191,40 +166,15 @@ protected JsonDeserializer findStdDeserializer(DeserializationContext ctxt, // note: we do NOT check for custom deserializers here, caller has already // done that JsonDeserializer deser = findDefaultDeserializer(ctxt, type, beanDesc); + // Also: better ensure these are post-processable? if (deser != null) { - return deser; - } - - Class cls = type.getRawClass(); - // [JACKSON-283]: AtomicReference is a rather special type... - if (AtomicReference.class.isAssignableFrom(cls)) { - // Must find parameterization - TypeFactory tf = ctxt.getTypeFactory(); - JavaType[] params = tf.findTypeParameters(type, AtomicReference.class); - JavaType referencedType; - if (params == null || params.length < 1) { // untyped (raw) - referencedType = TypeFactory.unknownType(); - } else { - referencedType = params[0]; + if (_factoryConfig.hasDeserializerModifiers()) { + for (BeanDeserializerModifier mod : _factoryConfig.deserializerModifiers()) { + deser = mod.modifyDeserializer(ctxt.getConfig(), beanDesc, deser); + } } - TypeDeserializer valueTypeDeser = findTypeDeserializer(ctxt.getConfig(), referencedType); - BeanDescription refdDesc = ctxt.getConfig().introspectClassAnnotations(referencedType); - deser = findDeserializerFromAnnotation(ctxt, refdDesc.getClassInfo()); - return new AtomicReferenceDeserializer(referencedType, valueTypeDeser, deser); } - return findOptionalStdDeserializer(ctxt, type, beanDesc); - } - - /** - * Overridable method called after checking all other types. - * - * @since 2.2 - */ - protected JsonDeserializer findOptionalStdDeserializer(DeserializationContext ctxt, - JavaType type, BeanDescription beanDesc) - throws JsonMappingException - { - return OptionalHandlerFactory.instance.findDeserializer(type, ctxt.getConfig(), beanDesc); + return deser; } protected JavaType materializeAbstractType(DeserializationContext ctxt, 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 new file mode 100644 index 0000000000..35d25e0a4c --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java @@ -0,0 +1,246 @@ +package com.fasterxml.jackson.databind.deser.std; + +import java.io.IOException; +import java.util.*; + +import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.deser.*; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; + +/** + * Basic serializer that can take JSON "Object" structure and + * construct a {@link java.util.Map} instance, with typed contents. + *

+ * Note: for untyped content (one indicated by passing Object.class + * as the type), {@link UntypedObjectDeserializer} is used instead. + * It can also construct {@link java.util.Map}s, but not with specific + * POJO types, only other containers and primitives/wrappers. + */ +@JacksonStdImpl +public class MapEntryDeserializer + extends ContainerDeserializerBase> + implements ContextualDeserializer +{ + private static final long serialVersionUID = 1; + + // // Configuration: typing, deserializers + + protected final JavaType _type; + + /** + * Key deserializer to use; either passed via constructor + * (when indicated by annotations), or resolved when + * {@link #resolve} is called; + */ + protected final KeyDeserializer _keyDeserializer; + + /** + * Value deserializer. + */ + protected final JsonDeserializer _valueDeserializer; + + /** + * If value instances have polymorphic type information, this + * is the type deserializer that can handle it + */ + protected final TypeDeserializer _valueTypeDeserializer; + + /* + /********************************************************** + /* Life-cycle + /********************************************************** + */ + + public MapEntryDeserializer(JavaType type, + KeyDeserializer keyDeser, JsonDeserializer valueDeser, + TypeDeserializer valueTypeDeser) + { + super(type); + if (type.containedTypeCount() != 2) { // sanity check + throw new IllegalArgumentException("Missing generic type information for "+type); + } + _type = type; + _keyDeserializer = keyDeser; + _valueDeserializer = valueDeser; + _valueTypeDeserializer = valueTypeDeser; + } + + /** + * Copy-constructor that can be used by sub-classes to allow + * copy-on-write styling copying of settings of an existing instance. + */ + protected MapEntryDeserializer(MapEntryDeserializer src) + { + super(src._type); + _type = src._type; + _keyDeserializer = src._keyDeserializer; + _valueDeserializer = src._valueDeserializer; + _valueTypeDeserializer = src._valueTypeDeserializer; + } + + protected MapEntryDeserializer(MapEntryDeserializer src, + KeyDeserializer keyDeser, JsonDeserializer valueDeser, + TypeDeserializer valueTypeDeser) + { + super(src._type); + _type = src._type; + _keyDeserializer = keyDeser; + _valueDeserializer = valueDeser; + _valueTypeDeserializer = valueTypeDeser; + } + + /** + * Fluent factory method used to create a copy with slightly + * different settings. When sub-classing, MUST be overridden. + */ + @SuppressWarnings("unchecked") + protected MapEntryDeserializer withResolved(KeyDeserializer keyDeser, + TypeDeserializer valueTypeDeser, JsonDeserializer valueDeser) + { + + if ((_keyDeserializer == keyDeser) && (_valueDeserializer == valueDeser) + && (_valueTypeDeserializer == valueTypeDeser)) { + return this; + } + return new MapEntryDeserializer(this, + keyDeser, (JsonDeserializer) valueDeser, valueTypeDeser); + } + + /* + /********************************************************** + /* Validation, post-processing (ResolvableDeserializer) + /********************************************************** + */ + + /** + * Method called to finalize setup of this deserializer, + * when it is known for which property deserializer is needed for. + */ + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) throws JsonMappingException + { + KeyDeserializer kd = _keyDeserializer; + if (kd == null) { + kd = ctxt.findKeyDeserializer(_type.containedType(0), property); + } else { + if (kd instanceof ContextualKeyDeserializer) { + kd = ((ContextualKeyDeserializer) kd).createContextual(ctxt, property); + } + } + JsonDeserializer vd = _valueDeserializer; + vd = findConvertingContentDeserializer(ctxt, property, vd); + if (vd == null) { + vd = ctxt.findContextualValueDeserializer(_type.containedType(1), property); + } else { // if directly assigned, probably not yet contextual, so: + vd = ctxt.handleSecondaryContextualization(vd, property); + } + TypeDeserializer vtd = _valueTypeDeserializer; + if (vtd != null) { + vtd = vtd.forProperty(property); + } + return withResolved(kd, vtd, vd); + } + + /* + /********************************************************** + /* ContainerDeserializerBase API + /********************************************************** + */ + + @Override + public JavaType getContentType() { + return _type.containedType(1); + } + + @Override + public JsonDeserializer getContentDeserializer() { + return _valueDeserializer; + } + + /* + /********************************************************** + /* JsonDeserializer API + /********************************************************** + */ + + @Override + public Map.Entry deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException + { + // Ok: must point to START_OBJECT, FIELD_NAME or END_OBJECT + JsonToken t = jp.getCurrentToken(); + if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) { + // [JACKSON-620] (empty) String may be ok however: + // slightly redundant (since String was passed above), but + return _deserializeFromEmpty(jp, ctxt); + } + if (t == JsonToken.START_OBJECT) { + t = jp.nextToken(); + } + if (t != JsonToken.FIELD_NAME) { + if (t == JsonToken.END_OBJECT) { + throw ctxt.mappingException("Can not deserialize a Map.Entry out of empty JSON Object"); + } + throw ctxt.mappingException(handledType(), t); + } + + final KeyDeserializer keyDes = _keyDeserializer; + final JsonDeserializer valueDes = _valueDeserializer; + final TypeDeserializer typeDeser = _valueTypeDeserializer; + + final String keyStr = jp.getCurrentName(); + Object key = keyDes.deserializeKey(keyStr, ctxt); + Object value = null; + // And then the value... + t = jp.nextToken(); + try { + // Note: must handle null explicitly here; value deserializers won't + if (t == JsonToken.VALUE_NULL) { + value = valueDes.getNullValue(); + } else if (typeDeser == null) { + value = valueDes.deserialize(jp, ctxt); + } else { + value = valueDes.deserializeWithType(jp, ctxt, typeDeser); + } + } catch (Exception e) { + wrapAndThrow(e, Map.Entry.class, keyStr); + } + + // Close, but also verify that we reached the END_OBJECT + t = jp.nextToken(); + if (t != JsonToken.END_OBJECT) { + if (t == JsonToken.FIELD_NAME) { // most likely + throw ctxt.mappingException("Problem binding JSON into Map.Entry: more than entry in JSON (second field: '"+jp.getCurrentName()+"')"); + } + // how would this occur? + throw ctxt.mappingException("Problem binding JSON into Map.Entry: unexpected content after JSON Object entry: "+t); + } + return new AbstractMap.SimpleEntry(key, value); + } + + @Override + public Map.Entry deserialize(JsonParser jp, DeserializationContext ctxt, + Map.Entry result) throws IOException + { + throw new IllegalStateException("Can not update Map.Entry values"); + } + + @Override + public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, + TypeDeserializer typeDeserializer) + throws IOException, JsonProcessingException + { + // In future could check current token... for now this should be enough: + return typeDeserializer.deserializeTypedFromObject(jp, ctxt); + } + + /* + /********************************************************** + /* Other public accessors + /********************************************************** + */ + + @Override public JavaType getValueType() { return _type; } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java index 74eaa165b9..94a3ac59f3 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java @@ -484,13 +484,43 @@ public void testKeyWithCreator() throws Exception * Simple test to ensure that @JsonDeserialize.using is * recognized */ - public void testMapWithDeserializer() throws IOException + public void testMapWithDeserializer() throws Exception { CustomMap result = MAPPER.readValue(quote("xyz"), CustomMap.class); assertEquals(1, result.size()); assertEquals("xyz", result.get("x")); } + /* + /********************************************************** + /* Test methods, annotated Map.Entry + /********************************************************** + */ + + public void testMapEntrySimpleTypes() throws Exception + { + List> stuff = MAPPER.readValue(aposToQuotes("[{'a':15},{'b':42}]"), + new TypeReference>>() { }); + assertNotNull(stuff); + assertEquals(2, stuff.size()); + assertNotNull(stuff.get(1)); + assertEquals("b", stuff.get(1).getKey()); + assertEquals(Long.valueOf(42), stuff.get(1).getValue()); + } + + public void testMapEntryWithStringBean() throws Exception + { + List> stuff = MAPPER.readValue(aposToQuotes("[{'28':'Foo'},{'13':'Bar'}]"), + new TypeReference>>() { }); + assertNotNull(stuff); + assertEquals(2, stuff.size()); + assertNotNull(stuff.get(1)); + assertEquals(Integer.valueOf(13), stuff.get(1).getKey()); + + StringWrapper sw = stuff.get(1).getValue(); + assertEquals("Bar", sw.str); + } + /* /********************************************************** /* Error tests diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestMapSerialization.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestMapSerialization.java index cc265f0621..ad9b9be448 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/TestMapSerialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestMapSerialization.java @@ -183,5 +183,9 @@ public void testEnumMapEntry() throws IOException StringIntMapEntry input = new StringIntMapEntry("answer", 42); String json = MAPPER.writeValueAsString(input); assertEquals(aposToQuotes("{'answer':42}"), json); + + StringIntMapEntry[] array = new StringIntMapEntry[] { input }; + json = MAPPER.writeValueAsString(array); + assertEquals(aposToQuotes("[{'answer':42}]"), json); } }