| 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 |
| { |
| private static final long serialVersionUID = -3378654289961736240L; |
| |
| // // 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); |
| } |
| } |