| package com.fasterxml.jackson.databind.deser.std; |
| |
| import java.io.IOException; |
| import java.util.Collection; |
| |
| import com.fasterxml.jackson.core.*; |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; |
| import com.fasterxml.jackson.databind.deser.ContextualDeserializer; |
| import com.fasterxml.jackson.databind.deser.ValueInstantiator; |
| import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams; |
| import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; |
| |
| /** |
| * Specifically optimized version for {@link java.util.Collection}s |
| * that contain String values; reason is that this is a very common |
| * type and we can make use of the fact that Strings are final. |
| */ |
| @JacksonStdImpl |
| public final class StringCollectionDeserializer |
| extends ContainerDeserializerBase<Collection<String>> |
| implements ContextualDeserializer |
| { |
| private static final long serialVersionUID = 1L; |
| |
| // // Configuration |
| |
| protected final JavaType _collectionType; |
| |
| /** |
| * Value deserializer to use, if NOT the standard one |
| * (if it is, will be null). |
| */ |
| protected final JsonDeserializer<String> _valueDeserializer; |
| |
| // // Instance construction settings: |
| |
| /** |
| * Instantiator used in case custom handling is needed for creation. |
| */ |
| protected final ValueInstantiator _valueInstantiator; |
| |
| /** |
| * Deserializer that is used iff delegate-based creator is |
| * to be used for deserializing from JSON Object. |
| */ |
| protected final JsonDeserializer<Object> _delegateDeserializer; |
| |
| // NOTE: no PropertyBasedCreator, as JSON Arrays have no properties |
| |
| /* |
| /********************************************************** |
| /* Life-cycle |
| /********************************************************** |
| */ |
| |
| public StringCollectionDeserializer(JavaType collectionType, |
| JsonDeserializer<?> valueDeser, ValueInstantiator valueInstantiator) |
| { |
| this(collectionType, valueInstantiator, null, valueDeser); |
| } |
| |
| @SuppressWarnings("unchecked") |
| protected StringCollectionDeserializer(JavaType collectionType, |
| ValueInstantiator valueInstantiator, JsonDeserializer<?> delegateDeser, |
| JsonDeserializer<?> valueDeser) |
| { |
| super(collectionType); |
| _collectionType = collectionType; |
| _valueDeserializer = (JsonDeserializer<String>) valueDeser; |
| _valueInstantiator = valueInstantiator; |
| _delegateDeserializer = (JsonDeserializer<Object>) delegateDeser; |
| } |
| |
| protected StringCollectionDeserializer withResolved(JsonDeserializer<?> delegateDeser, |
| JsonDeserializer<?> valueDeser) |
| { |
| if ((_valueDeserializer == valueDeser) && (_delegateDeserializer == delegateDeser)) { |
| return this; |
| } |
| return new StringCollectionDeserializer(_collectionType, |
| _valueInstantiator, delegateDeser, valueDeser); |
| } |
| |
| /* |
| /********************************************************** |
| /* Validation, post-processing |
| /********************************************************** |
| */ |
| @Override |
| public JsonDeserializer<?> createContextual(DeserializationContext ctxt, |
| BeanProperty property) throws JsonMappingException |
| { |
| // May need to resolve types for delegate-based creators: |
| JsonDeserializer<Object> delegate = null; |
| if (_valueInstantiator != null) { |
| AnnotatedWithParams delegateCreator = _valueInstantiator.getDelegateCreator(); |
| if (delegateCreator != null) { |
| JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig()); |
| delegate = findDeserializer(ctxt, delegateType, property); |
| } |
| } |
| JsonDeserializer<?> valueDeser = _valueDeserializer; |
| 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); |
| } |
| } else { // if directly assigned, probably not yet contextual, so: |
| valueDeser = ctxt.handleContextualization(valueDeser, property); |
| } |
| if (isDefaultDeserializer(valueDeser)) { |
| valueDeser = null; |
| } |
| return withResolved(delegate, valueDeser); |
| } |
| |
| /* |
| /********************************************************** |
| /* ContainerDeserializerBase API |
| /********************************************************** |
| */ |
| |
| @Override |
| public JavaType getContentType() { |
| return _collectionType.getContentType(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public JsonDeserializer<Object> getContentDeserializer() { |
| JsonDeserializer<?> deser = _valueDeserializer; |
| return (JsonDeserializer<Object>) deser; |
| } |
| |
| /* |
| /********************************************************** |
| /* JsonDeserializer API |
| /********************************************************** |
| */ |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public Collection<String> deserialize(JsonParser jp, DeserializationContext ctxt) |
| throws IOException, JsonProcessingException |
| { |
| if (_delegateDeserializer != null) { |
| return (Collection<String>) _valueInstantiator.createUsingDelegate(ctxt, |
| _delegateDeserializer.deserialize(jp, ctxt)); |
| } |
| final Collection<String> result = (Collection<String>) _valueInstantiator.createUsingDefault(ctxt); |
| return deserialize(jp, ctxt, result); |
| } |
| |
| @Override |
| public Collection<String> deserialize(JsonParser jp, DeserializationContext ctxt, |
| Collection<String> result) |
| throws IOException, JsonProcessingException |
| { |
| // Ok: must point to START_ARRAY |
| if (!jp.isExpectedStartArrayToken()) { |
| return handleNonArray(jp, ctxt, result); |
| } |
| |
| if (_valueDeserializer != null) { |
| return deserializeUsingCustom(jp, ctxt, result, _valueDeserializer); |
| } |
| JsonToken t; |
| |
| while ((t = jp.nextToken()) != JsonToken.END_ARRAY) { |
| result.add((t == JsonToken.VALUE_NULL) ? null : _parseString(jp, ctxt)); |
| } |
| return result; |
| } |
| |
| private Collection<String> deserializeUsingCustom(JsonParser jp, DeserializationContext ctxt, |
| Collection<String> result, final JsonDeserializer<String> deser) |
| throws IOException, JsonProcessingException |
| { |
| JsonToken t; |
| while ((t = jp.nextToken()) != JsonToken.END_ARRAY) { |
| String value; |
| |
| if (t == JsonToken.VALUE_NULL) { |
| value = null; |
| } else { |
| value = deser.deserialize(jp, ctxt); |
| } |
| result.add(value); |
| } |
| 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.deserializeTypedFromArray(jp, ctxt); |
| } |
| |
| /** |
| * Helper method called when current token is no START_ARRAY. Will either |
| * throw an exception, or try to handle value as if member of implicit |
| * array, depending on configuration. |
| */ |
| private final Collection<String> handleNonArray(JsonParser jp, DeserializationContext ctxt, |
| Collection<String> result) |
| throws IOException, JsonProcessingException |
| { |
| // [JACKSON-526]: implicit arrays from single values? |
| if (!ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)) { |
| throw ctxt.mappingException(_collectionType.getRawClass()); |
| } |
| // Strings are one of "native" (intrinsic) types, so there's never type deserializer involved |
| JsonDeserializer<String> valueDes = _valueDeserializer; |
| JsonToken t = jp.getCurrentToken(); |
| |
| String value; |
| |
| if (t == JsonToken.VALUE_NULL) { |
| value = null; |
| } else { |
| value = (valueDes == null) ? _parseString(jp, ctxt) : valueDes.deserialize(jp, ctxt); |
| } |
| result.add(value); |
| return result; |
| } |
| } |