All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fitbur.jackson.databind.deser.std.CollectionDeserializer Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
package com.fitbur.jackson.databind.deser.std;

import java.io.IOException;
import java.util.*;

import com.fitbur.jackson.annotation.JsonFormat;
import com.fitbur.jackson.core.*;
import com.fitbur.jackson.databind.*;
import com.fitbur.jackson.databind.annotation.JacksonStdImpl;
import com.fitbur.jackson.databind.deser.ContextualDeserializer;
import com.fitbur.jackson.databind.deser.UnresolvedForwardReference;
import com.fitbur.jackson.databind.deser.ValueInstantiator;
import com.fitbur.jackson.databind.deser.impl.ReadableObjectId.Referring;
import com.fitbur.jackson.databind.deser.std.ContainerDeserializerBase;
import com.fitbur.jackson.databind.jsontype.TypeDeserializer;

/**
 * Basic serializer that can take JSON "Array" structure and
 * construct a {@link java.util.Collection} 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.List}s, but not with specific * POJO types, only other containers and primitives/wrappers. */ @JacksonStdImpl public class CollectionDeserializer extends ContainerDeserializerBase> implements ContextualDeserializer { private static final long serialVersionUID = -1L; // since 2.5 // // Configuration protected final JavaType _collectionType; /** * Value deserializer. */ protected final JsonDeserializer _valueDeserializer; /** * If element 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; /** * Deserializer that is used iff delegate-based creator is * to be used for deserializing from JSON Object. */ protected final JsonDeserializer _delegateDeserializer; /** * Specific override for this instance (from proper, or global per-type overrides) * to indicate whether single value may be taken to mean an unwrapped one-element array * or not. If null, left to global defaults. * * @since 2.7 */ protected final Boolean _unwrapSingle; // NOTE: no PropertyBasedCreator, as JSON Arrays have no properties /* /********************************************************** /* Life-cycle /********************************************************** */ /** * Constructor for context-free instances, where we do not yet know * which property is using this deserializer. */ public CollectionDeserializer(JavaType collectionType, JsonDeserializer valueDeser, TypeDeserializer valueTypeDeser, ValueInstantiator valueInstantiator) { this(collectionType, valueDeser, valueTypeDeser, valueInstantiator, null, null); } /** * Constructor used when creating contextualized instances. */ protected CollectionDeserializer(JavaType collectionType, JsonDeserializer valueDeser, TypeDeserializer valueTypeDeser, ValueInstantiator valueInstantiator, JsonDeserializer delegateDeser, Boolean unwrapSingle) { super(collectionType); _collectionType = collectionType; _valueDeserializer = valueDeser; _valueTypeDeserializer = valueTypeDeser; _valueInstantiator = valueInstantiator; _delegateDeserializer = delegateDeser; _unwrapSingle = unwrapSingle; } /** * Copy-constructor that can be used by sub-classes to allow * copy-on-write styling copying of settings of an existing instance. */ protected CollectionDeserializer(CollectionDeserializer src) { super(src._collectionType); _collectionType = src._collectionType; _valueDeserializer = src._valueDeserializer; _valueTypeDeserializer = src._valueTypeDeserializer; _valueInstantiator = src._valueInstantiator; _delegateDeserializer = src._delegateDeserializer; _unwrapSingle = src._unwrapSingle; } /** * Fluent-factory method call to construct contextual instance. * * @since 2.7 */ @SuppressWarnings("unchecked") protected CollectionDeserializer withResolved(JsonDeserializer dd, JsonDeserializer vd, TypeDeserializer vtd, Boolean unwrapSingle) { if ((dd == _delegateDeserializer) && (vd == _valueDeserializer) && (vtd == _valueTypeDeserializer) && (_unwrapSingle == unwrapSingle)) { return this; } return new CollectionDeserializer(_collectionType, (JsonDeserializer) vd, vtd, _valueInstantiator, (JsonDeserializer) dd, unwrapSingle); } /** * @deprecated Since 2.7 as it does not pass `unwrapSingle` */ @Deprecated // since 2.7 -- will not retain "unwrapSingle" setting protected CollectionDeserializer withResolved(JsonDeserializer dd, JsonDeserializer vd, TypeDeserializer vtd) { return withResolved(dd, vd, vtd, _unwrapSingle); } // Important: do NOT cache if polymorphic values @Override // since 2.5 public boolean isCachable() { // 26-Mar-2015, tatu: As per [databind#735], need to be careful return (_valueDeserializer == null) && (_valueTypeDeserializer == null) && (_delegateDeserializer == null) ; } /* /********************************************************** /* 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 CollectionDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { // May need to resolve types for delegate-based creators: JsonDeserializer delegateDeser = null; if ((_valueInstantiator != null) && _valueInstantiator.canCreateUsingDelegate()) { JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig()); if (delegateType == null) { throw new IllegalArgumentException("Invalid delegate-creator definition for "+_collectionType +": value instantiator ("+_valueInstantiator.getClass().getName() +") returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'"); } delegateDeser = findDeserializer(ctxt, delegateType, property); } // [databind#1043]: allow per-property allow-wrapping of single overrides: // 11-Dec-2015, tatu: Should we pass basic `Collection.class`, or more refined? Mostly // comes down to "List vs Collection" I suppose... for now, pass Collection Boolean unwrapSingle = findFormatFeature(ctxt, property, Collection.class, JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY); // also, often value deserializer is resolved here: JsonDeserializer valueDeser = _valueDeserializer; // May have a content converter valueDeser = findConvertingContentDeserializer(ctxt, property, valueDeser); final JavaType vt = _collectionType.getContentType(); if (valueDeser == null) { valueDeser = ctxt.findContextualValueDeserializer(vt, property); } else { // if directly assigned, probably not yet contextual, so: valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt); } // and finally, type deserializer needs context as well TypeDeserializer valueTypeDeser = _valueTypeDeserializer; if (valueTypeDeser != null) { valueTypeDeser = valueTypeDeser.forProperty(property); } return withResolved(delegateDeser, valueDeser, valueTypeDeser, unwrapSingle); } /* /********************************************************** /* ContainerDeserializerBase API /********************************************************** */ @Override public JavaType getContentType() { return _collectionType.getContentType(); } @Override public JsonDeserializer getContentDeserializer() { return _valueDeserializer; } /* /********************************************************** /* JsonDeserializer API /********************************************************** */ @SuppressWarnings("unchecked") @Override public Collection deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { if (_delegateDeserializer != null) { return (Collection) _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(p, ctxt)); } /* [JACKSON-620]: empty String may be ok; bit tricky to check, however, since * there is also possibility of "auto-wrapping" of single-element arrays. * Hence we only accept empty String here. */ if (p.hasToken(JsonToken.VALUE_STRING)) { String str = p.getText(); if (str.length() == 0) { return (Collection) _valueInstantiator.createFromString(ctxt, str); } } return deserialize(p, ctxt, (Collection) _valueInstantiator.createUsingDefault(ctxt)); } @Override public Collection deserialize(JsonParser p, DeserializationContext ctxt, Collection result) throws IOException { // Ok: must point to START_ARRAY (or equivalent) if (!p.isExpectedStartArrayToken()) { return handleNonArray(p, ctxt, result); } // [databind#631]: Assign current value, to be accessible by custom serializers p.setCurrentValue(result); JsonDeserializer valueDes = _valueDeserializer; final TypeDeserializer typeDeser = _valueTypeDeserializer; CollectionReferringAccumulator referringAccumulator = (valueDes.getObjectIdReader() == null) ? null : new CollectionReferringAccumulator(_collectionType.getContentType().getRawClass(), result); JsonToken t; while ((t = p.nextToken()) != JsonToken.END_ARRAY) { try { Object value; if (t == JsonToken.VALUE_NULL) { value = valueDes.getNullValue(ctxt); } else if (typeDeser == null) { value = valueDes.deserialize(p, ctxt); } else { value = valueDes.deserializeWithType(p, ctxt, typeDeser); } if (referringAccumulator != null) { referringAccumulator.add(value); } else { result.add(value); } } catch (UnresolvedForwardReference reference) { if (referringAccumulator == null) { throw JsonMappingException .from(p, "Unresolved forward reference but no identity info", reference); } Referring ref = referringAccumulator.handleUnresolvedReference(reference); reference.getRoid().appendReferring(ref); } catch (Exception e) { boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS); if (!wrap && e instanceof RuntimeException) { throw (RuntimeException)e; } throw JsonMappingException.wrapWithPath(e, result, result.size()); } } return result; } @Override public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { // 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. */ protected final Collection handleNonArray(JsonParser p, DeserializationContext ctxt, Collection result) throws IOException { // Implicit arrays from single values? boolean canWrap = (_unwrapSingle == Boolean.TRUE) || ((_unwrapSingle == null) && ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)); if (!canWrap) { throw ctxt.mappingException(_collectionType.getRawClass()); } JsonDeserializer valueDes = _valueDeserializer; final TypeDeserializer typeDeser = _valueTypeDeserializer; JsonToken t = p.getCurrentToken(); Object value; try { if (t == JsonToken.VALUE_NULL) { value = valueDes.getNullValue(ctxt); } else if (typeDeser == null) { value = valueDes.deserialize(p, ctxt); } else { value = valueDes.deserializeWithType(p, ctxt, typeDeser); } } catch (Exception e) { // note: pass Object.class, not Object[].class, as we need element type for error info throw JsonMappingException.wrapWithPath(e, Object.class, result.size()); } result.add(value); return result; } public final static class CollectionReferringAccumulator { private final Class _elementType; private final Collection _result; /** * A list of {@link CollectionReferring} to maintain ordering. */ private List _accumulator = new ArrayList(); public CollectionReferringAccumulator(Class elementType, Collection result) { _elementType = elementType; _result = result; } public void add(Object value) { if (_accumulator.isEmpty()) { _result.add(value); } else { CollectionReferring ref = _accumulator.get(_accumulator.size() - 1); ref.next.add(value); } } public Referring handleUnresolvedReference(UnresolvedForwardReference reference) { CollectionReferring id = new CollectionReferring(this, reference, _elementType); _accumulator.add(id); return id; } public void resolveForwardReference(Object id, Object value) throws IOException { Iterator iterator = _accumulator.iterator(); // Resolve ordering after resolution of an id. This mean either: // 1- adding to the result collection in case of the first unresolved id. // 2- merge the content of the resolved id with its previous unresolved id. Collection previous = _result; while (iterator.hasNext()) { CollectionReferring ref = iterator.next(); if (ref.hasId(id)) { iterator.remove(); previous.add(value); previous.addAll(ref.next); return; } previous = ref.next; } throw new IllegalArgumentException("Trying to resolve a forward reference with id [" + id + "] that wasn't previously seen as unresolved."); } } /** * Helper class to maintain processing order of value. The resolved * object associated with {@link #_id} comes before the values in * {@link #next}. */ private final static class CollectionReferring extends Referring { private final CollectionReferringAccumulator _parent; public final List next = new ArrayList(); CollectionReferring(CollectionReferringAccumulator parent, UnresolvedForwardReference reference, Class contentType) { super(reference, contentType); _parent = parent; } @Override public void handleResolvedForwardReference(Object id, Object value) throws IOException { _parent.resolveForwardReference(id, value); } } }