package com.fasterxml.jackson.databind.deser.std; import java.io.IOException; import java.lang.reflect.InvocationTargetException; 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.deser.impl.PropertyBasedCreator; import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer; import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.util.ArrayBuilders; /** * Basic serializer that can take Json "Object" structure and * construct a {@link java.util.Map} instance, with typed contents. *<p> * 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 MapDeserializer extends ContainerDeserializerBase<Map<Object,Object>> implements ContextualDeserializer, ResolvableDeserializer { // // Configuration: typing, deserializers protected final JavaType _mapType; /** * Key deserializer to use; either passed via constructor * (when indicated by annotations), or resolved when * {@link #resolve} is called; */ protected final KeyDeserializer _keyDeserializer; /** * Flag set to indicate that the key type is * {@link java.lang.String} (or {@link java.lang.Object}, for * which String is acceptable), <b>and</b> that the * default Jackson key deserializer would be used. * If both are true, can optimize handling. */ protected boolean _standardStringKey; /** * Value deserializer. */ protected final JsonDeserializer<Object> _valueDeserializer; /** * If value instances have polymorphic type information, this * is the type deserializer that can handle it */ protected final TypeDeserializer _valueTypeDeserializer; // // Instance construction settings: protected final ValueInstantiator _valueInstantiator; protected final boolean _hasDefaultCreator; /** * Deserializer that is used iff delegate-based creator is * to be used for deserializing from JSON Object. */ protected JsonDeserializer<Object> _delegateDeserializer; /** * If the Map is to be instantiated using non-default constructor * or factory method * that takes one or more named properties as argument(s), * this creator is used for instantiation. */ protected PropertyBasedCreator _propertyBasedCreator; // // Any properties to ignore if seen? protected HashSet<String> _ignorableProperties; /* /********************************************************** /* Life-cycle /********************************************************** */ public MapDeserializer(JavaType mapType, ValueInstantiator valueInstantiator, KeyDeserializer keyDeser, JsonDeserializer<Object> valueDeser, TypeDeserializer valueTypeDeser) { super(Map.class); _mapType = mapType; _keyDeserializer = keyDeser; _valueDeserializer = valueDeser; _valueTypeDeserializer = valueTypeDeser; _valueInstantiator = valueInstantiator; _hasDefaultCreator = valueInstantiator.canCreateUsingDefault(); _delegateDeserializer = null; _propertyBasedCreator = null; _standardStringKey = _isStdKeyDeser(mapType, keyDeser); } /** * Copy-constructor that can be used by sub-classes to allow * copy-on-write styling copying of settings of an existing instance. */ protected MapDeserializer(MapDeserializer src) { super(src._valueClass); _mapType = src._mapType; _keyDeserializer = src._keyDeserializer; _valueDeserializer = src._valueDeserializer; _valueTypeDeserializer = src._valueTypeDeserializer; _valueInstantiator = src._valueInstantiator; _propertyBasedCreator = src._propertyBasedCreator; _delegateDeserializer = src._delegateDeserializer; _hasDefaultCreator = src._hasDefaultCreator; // should we make a copy here? _ignorableProperties = src._ignorableProperties; _standardStringKey = src._standardStringKey; } protected MapDeserializer(MapDeserializer src, KeyDeserializer keyDeser, JsonDeserializer<Object> valueDeser, TypeDeserializer valueTypeDeser, HashSet<String> ignorable) { super(src._valueClass); _mapType = src._mapType; _keyDeserializer = keyDeser; _valueDeserializer = valueDeser; _valueTypeDeserializer = valueTypeDeser; _valueInstantiator = src._valueInstantiator; _propertyBasedCreator = src._propertyBasedCreator; _delegateDeserializer = src._delegateDeserializer; _hasDefaultCreator = src._hasDefaultCreator; _ignorableProperties = ignorable; _standardStringKey = _isStdKeyDeser(_mapType, keyDeser); } /** * Fluent factory method used to create a copy with slightly * different settings. When sub-classing, MUST be overridden. */ @SuppressWarnings("unchecked") protected MapDeserializer withResolved(KeyDeserializer keyDeser, TypeDeserializer valueTypeDeser, JsonDeserializer<?> valueDeser, HashSet<String> ignorable) { if ((_keyDeserializer == keyDeser) && (_valueDeserializer == valueDeser) && (_valueTypeDeserializer == valueTypeDeser) && (_ignorableProperties == ignorable)) { return this; } return new MapDeserializer(this, keyDeser, (JsonDeserializer<Object>) valueDeser, valueTypeDeser, ignorable); } /** * Helper method used to check whether we can just use the default key * deserialization, where JSON String becomes Java String. */ protected final boolean _isStdKeyDeser(JavaType mapType, KeyDeserializer keyDeser) { if (keyDeser == null) { return true; } JavaType keyType = mapType.getKeyType(); if (keyType == null) { // assumed to be Object return true; } Class<?> rawKeyType = keyType.getRawClass(); return ((rawKeyType == String.class || rawKeyType == Object.class) && isDefaultKeyDeserializer(keyDeser)); } public void setIgnorableProperties(String[] ignorable) { _ignorableProperties = (ignorable == null || ignorable.length == 0) ? null : ArrayBuilders.arrayToSet(ignorable); } /* /********************************************************** /* Validation, post-processing (ResolvableDeserializer) /********************************************************** */ // @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException { // May need to resolve types for delegate- and/or property-based creators: if (_valueInstantiator.canCreateUsingDelegate()) { JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig()); if (delegateType == null) { throw new IllegalArgumentException("Invalid delegate-creator definition for "+_mapType +": value instantiator ("+_valueInstantiator.getClass().getName() +") returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'"); } /* Theoretically should be able to get CreatorProperty for delegate * parameter to pass; but things get tricky because DelegateCreator * may contain injectable values. So, for now, let's pass nothing. */ _delegateDeserializer = findDeserializer(ctxt, delegateType, null); } if (_valueInstantiator.canCreateFromObjectWith()) { SettableBeanProperty[] creatorProps = _valueInstantiator.getFromObjectArguments(ctxt.getConfig()); _propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, creatorProps); } _standardStringKey = _isStdKeyDeser(_mapType, _keyDeserializer); } /** * 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(_mapType.getKeyType(), property); } else { if (kd instanceof ContextualKeyDeserializer) { kd = ((ContextualKeyDeserializer) kd).createContextual(ctxt, property); } } JsonDeserializer<?> vd = _valueDeserializer; if (vd == null) { vd = ctxt.findContextualValueDeserializer(_mapType.getContentType(), property); } else { // if directly assigned, probably not yet contextual, so: if (vd instanceof ContextualDeserializer) { vd = ((ContextualDeserializer) vd).createContextual(ctxt, property); } } TypeDeserializer vtd = _valueTypeDeserializer; if (vtd != null) { vtd = vtd.forProperty(property); } HashSet<String> ignored = _ignorableProperties; AnnotationIntrospector intr = ctxt.getAnnotationIntrospector(); if (intr != null && property != null) { String[] moreToIgnore = intr.findPropertiesToIgnore(property.getMember()); if (moreToIgnore != null) { ignored = (ignored == null) ? new HashSet<String>() : new HashSet<String>(ignored); for (String str : moreToIgnore) { ignored.add(str); } } } return withResolved(kd, vtd, vd, ignored); } /* /********************************************************** /* ContainerDeserializerBase API /********************************************************** */ @Override public JavaType getContentType() { return _mapType.getContentType(); } @Override public JsonDeserializer<Object> getContentDeserializer() { return _valueDeserializer; } /* /********************************************************** /* JsonDeserializer API /********************************************************** */ @Override @SuppressWarnings("unchecked") public Map<Object,Object> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { if (_propertyBasedCreator != null) { return _deserializeUsingCreator(jp, ctxt); } if (_delegateDeserializer != null) { return (Map<Object,Object>) _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt)); } if (!_hasDefaultCreator) { throw ctxt.instantiationException(getMapClass(), "No default constructor found"); } // 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: if (t == JsonToken.VALUE_STRING) { return (Map<Object,Object>) _valueInstantiator.createFromString(ctxt, jp.getText()); } throw ctxt.mappingException(getMapClass()); } final Map<Object,Object> result = (Map<Object,Object>) _valueInstantiator.createUsingDefault(ctxt); if (_standardStringKey) { _readAndBindStringMap(jp, ctxt, result); return result; } _readAndBind(jp, ctxt, result); return result; } @Override public Map<Object,Object> deserialize(JsonParser jp, DeserializationContext ctxt, Map<Object,Object> result) throws IOException, JsonProcessingException { // Ok: must point to START_OBJECT or FIELD_NAME JsonToken t = jp.getCurrentToken(); if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME) { throw ctxt.mappingException(getMapClass()); } if (_standardStringKey) { _readAndBindStringMap(jp, ctxt, result); return result; } _readAndBind(jp, ctxt, result); return result; } @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 /********************************************************** */ @SuppressWarnings("unchecked") public final Class<?> getMapClass() { return (Class<Map<Object,Object>>) _mapType.getRawClass(); } @Override public JavaType getValueType() { return _mapType; } /* /********************************************************** /* Internal methods /********************************************************** */ protected final void _readAndBind(JsonParser jp, DeserializationContext ctxt, Map<Object,Object> result) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); if (t == JsonToken.START_OBJECT) { t = jp.nextToken(); } final KeyDeserializer keyDes = _keyDeserializer; final JsonDeserializer<Object> valueDes = _valueDeserializer; final TypeDeserializer typeDeser = _valueTypeDeserializer; for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) { // Must point to field name String fieldName = jp.getCurrentName(); Object key = keyDes.deserializeKey(fieldName, ctxt); // And then the value... t = jp.nextToken(); if (_ignorableProperties != null && _ignorableProperties.contains(fieldName)) { jp.skipChildren(); continue; } // Note: must handle null explicitly here; value deserializers won't Object value; if (t == JsonToken.VALUE_NULL) { value = null; } else if (typeDeser == null) { value = valueDes.deserialize(jp, ctxt); } else { value = valueDes.deserializeWithType(jp, ctxt, typeDeser); } /* !!! 23-Dec-2008, tatu: should there be an option to verify * that there are no duplicate field names? (and/or what * to do, keep-first or keep-last) */ result.put(key, value); } } /** * Optimized method used when keys can be deserialized as plain old * {@link java.lang.String}s, and there is no custom deserialized * specified. */ protected final void _readAndBindStringMap(JsonParser jp, DeserializationContext ctxt, Map<Object,Object> result) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); if (t == JsonToken.START_OBJECT) { t = jp.nextToken(); } final JsonDeserializer<Object> valueDes = _valueDeserializer; final TypeDeserializer typeDeser = _valueTypeDeserializer; for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) { // Must point to field name String fieldName = jp.getCurrentName(); // And then the value... t = jp.nextToken(); if (_ignorableProperties != null && _ignorableProperties.contains(fieldName)) { jp.skipChildren(); continue; } // Note: must handle null explicitly here; value deserializers won't Object value; if (t == JsonToken.VALUE_NULL) { value = null; } else if (typeDeser == null) { value = valueDes.deserialize(jp, ctxt); } else { value = valueDes.deserializeWithType(jp, ctxt, typeDeser); } result.put(fieldName, value); } } @SuppressWarnings("unchecked") public Map<Object,Object> _deserializeUsingCreator(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { final PropertyBasedCreator creator = _propertyBasedCreator; // null -> no ObjectIdReader for Maps (yet?) PropertyValueBuffer buffer = creator.startBuilding(jp, ctxt, null); JsonToken t = jp.getCurrentToken(); if (t == JsonToken.START_OBJECT) { t = jp.nextToken(); } final JsonDeserializer<Object> valueDes = _valueDeserializer; final TypeDeserializer typeDeser = _valueTypeDeserializer; for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) { String propName = jp.getCurrentName(); t = jp.nextToken(); // to get to value if (_ignorableProperties != null && _ignorableProperties.contains(propName)) { jp.skipChildren(); // and skip it (in case of array/object) continue; } // creator property? SettableBeanProperty prop = creator.findCreatorProperty(propName); if (prop != null) { // Last property to set? Object value = prop.deserialize(jp, ctxt); if (buffer.assignParameter(prop.getCreatorIndex(), value)) { jp.nextToken(); Map<Object,Object> result; try { result = (Map<Object,Object>)creator.build(ctxt, buffer); } catch (Exception e) { wrapAndThrow(e, _mapType.getRawClass()); return null; } _readAndBind(jp, ctxt, result); return result; } continue; } // other property? needs buffering String fieldName = jp.getCurrentName(); Object key = _keyDeserializer.deserializeKey(fieldName, ctxt); Object value; if (t == JsonToken.VALUE_NULL) { value = null; } else if (typeDeser == null) { value = valueDes.deserialize(jp, ctxt); } else { value = valueDes.deserializeWithType(jp, ctxt, typeDeser); } buffer.bufferMapProperty(key, value); } // end of JSON object? // if so, can just construct and leave... try { return (Map<Object,Object>)creator.build(ctxt, buffer); } catch (Exception e) { wrapAndThrow(e, _mapType.getRawClass()); return null; } } // note: copied form BeanDeserializer; should try to share somehow... protected void wrapAndThrow(Throwable t, Object ref) throws IOException { // to handle StackOverflow: while (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } // Errors and "plain" IOExceptions to be passed as is if (t instanceof Error) { throw (Error) t; } // ... except for mapping exceptions if (t instanceof IOException && !(t instanceof JsonMappingException)) { throw (IOException) t; } throw JsonMappingException.wrapWithPath(t, ref, null); } }