package com.fasterxml.jackson.databind.deser.std;
import java.io.IOException;
import java.util.*;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* 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
/**
* 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;
// 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, null);
}
/**
* Constructor used when creating contextualized instances.
*
* @since 2.9
*/
protected CollectionDeserializer(JavaType collectionType,
JsonDeserializer valueDeser, TypeDeserializer valueTypeDeser,
ValueInstantiator valueInstantiator, JsonDeserializer delegateDeser,
NullValueProvider nuller, Boolean unwrapSingle)
{
super(collectionType, nuller, unwrapSingle);
_valueDeserializer = valueDeser;
_valueTypeDeserializer = valueTypeDeser;
_valueInstantiator = valueInstantiator;
_delegateDeserializer = delegateDeser;
}
/**
* 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);
_valueDeserializer = src._valueDeserializer;
_valueTypeDeserializer = src._valueTypeDeserializer;
_valueInstantiator = src._valueInstantiator;
_delegateDeserializer = src._delegateDeserializer;
}
/**
* Fluent-factory method call to construct contextual instance.
*
* @since 2.9
*/
@SuppressWarnings("unchecked")
protected CollectionDeserializer withResolved(JsonDeserializer> dd,
JsonDeserializer> vd, TypeDeserializer vtd,
NullValueProvider nuller, Boolean unwrapSingle)
{
return new CollectionDeserializer(_containerType,
(JsonDeserializer) vd, vtd,
_valueInstantiator, (JsonDeserializer) dd,
nuller, 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)
;
}
@Override // since 2.12
public LogicalType logicalType() {
return LogicalType.Collection;
}
/*
/**********************************************************
/* 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) {
if (_valueInstantiator.canCreateUsingDelegate()) {
JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig());
if (delegateType == null) {
ctxt.reportBadDefinition(_containerType, String.format(
"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'",
_containerType,
_valueInstantiator.getClass().getName()));
}
delegateDeser = findDeserializer(ctxt, delegateType, property);
} else if (_valueInstantiator.canCreateUsingArrayDelegate()) {
JavaType delegateType = _valueInstantiator.getArrayDelegateType(ctxt.getConfig());
if (delegateType == null) {
ctxt.reportBadDefinition(_containerType, String.format(
"Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingArrayDelegate()', but null for 'getArrayDelegateType()'",
_containerType,
_valueInstantiator.getClass().getName()));
}
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 = _containerType.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);
}
NullValueProvider nuller = findContentNullProvider(ctxt, property, valueDeser);
if ((!Objects.equals(unwrapSingle, _unwrapSingle))
|| (nuller != _nullProvider)
|| (delegateDeser != _delegateDeserializer)
|| (valueDeser != _valueDeserializer)
|| (valueTypeDeser != _valueTypeDeserializer)
) {
return withResolved(delegateDeser, valueDeser, valueTypeDeser,
nuller, unwrapSingle);
}
return this;
}
/*
/**********************************************************
/* ContainerDeserializerBase API
/**********************************************************
*/
@Override
public JsonDeserializer getContentDeserializer() {
return _valueDeserializer;
}
@Override
public ValueInstantiator getValueInstantiator() {
return _valueInstantiator;
}
/*
/**********************************************************
/* 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));
}
// 16-May-2020, tatu: As per [dataformats-text#199] need to first check for
// possible Array-coercion and only after that String coercion
if (p.isExpectedStartArrayToken()) {
return _deserializeFromArray(p, ctxt, createDefaultInstance(ctxt));
}
// 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)) {
return _deserializeFromString(p, ctxt, p.getText());
}
return handleNonArray(p, ctxt, createDefaultInstance(ctxt));
}
/**
* @since 2.9
*/
@SuppressWarnings("unchecked")
protected Collection createDefaultInstance(DeserializationContext ctxt)
throws IOException
{
return (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 _deserializeFromArray(p, ctxt, result);
}
return handleNonArray(p, ctxt, result);
}
@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
throws IOException
{
// In future could check current token... for now this should be enough:
return typeDeserializer.deserializeTypedFromArray(p, ctxt);
}
/**
* Logic extracted to deal with incoming String value.
*
* @since 2.12
*/
@SuppressWarnings("unchecked")
protected Collection _deserializeFromString(JsonParser p, DeserializationContext ctxt,
String value)
throws IOException
{
final Class> rawTargetType = handledType();
// 05-Nov-2020, ckozak: As per [jackson-databind#2922] string values may be handled
// using handleNonArray, however empty strings may result in a null or empty collection
// depending on configuration.
// Start by verifying if we got empty/blank string since accessing
// CoercionAction may be costlier than String value we'll almost certainly
// need anyway
if (value.isEmpty()) {
CoercionAction act = ctxt.findCoercionAction(logicalType(), rawTargetType,
CoercionInputShape.EmptyString);
// handleNonArray may successfully deserialize the result (if
// ACCEPT_SINGLE_VALUE_AS_ARRAY is enabled, for example) otherwise it
// is capable of failing just as well as _deserializeFromEmptyString.
if (act != null && act != CoercionAction.Fail) {
return (Collection) _deserializeFromEmptyString(
p, ctxt, act, rawTargetType, "empty String (\"\")");
}
// note: `CoercionAction.Fail` falls through because we may need to allow
// `ACCEPT_SINGLE_VALUE_AS_ARRAY` handling later on
}
// 26-Mar-2021, tatu: Some day is today; as per [dataformat-xml#460],
// we do need to support blank String too...
else if (_isBlank(value)) {
final CoercionAction act = ctxt.findCoercionFromBlankString(logicalType(), rawTargetType,
CoercionAction.Fail);
if (act != CoercionAction.Fail) {
return (Collection) _deserializeFromEmptyString(
p, ctxt, act, rawTargetType, "blank String (all whitespace)");
}
// note: `CoercionAction.Fail` falls through because we may need to allow
// `ACCEPT_SINGLE_VALUE_AS_ARRAY` handling later on
}
return handleNonArray(p, ctxt, createDefaultInstance(ctxt));
}
/**
* @since 2.12
*/
protected Collection _deserializeFromArray(JsonParser p, DeserializationContext ctxt,
Collection result)
throws IOException
{
// [databind#631]: Assign current value, to be accessible by custom serializers
p.assignCurrentValue(result);
JsonDeserializer valueDes = _valueDeserializer;
// Let's offline handling of values with Object Ids (simplifies code here)
if (valueDes.getObjectIdReader() != null) {
return _deserializeWithObjectId(p, ctxt, result);
}
final TypeDeserializer typeDeser = _valueTypeDeserializer;
JsonToken t;
while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
try {
Object value;
if (t == JsonToken.VALUE_NULL) {
if (_skipNullValues) {
continue;
}
value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
}
if (value == null) {
_tryToAddNull(p, ctxt, result);
continue;
}
result.add(value);
/* 17-Dec-2017, tatu: should not occur at this level...
} catch (UnresolvedForwardReference reference) {
throw JsonMappingException
.from(p, "Unresolved forward reference but no identity info", reference);
*/
} catch (Exception e) {
boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
if (!wrap) {
ClassUtil.throwIfRTE(e);
}
throw JsonMappingException.wrapWithPath(e, result, result.size());
}
}
return result;
}
/**
* 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.
*/
@SuppressWarnings("unchecked")
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) {
return (Collection) ctxt.handleUnexpectedToken(_containerType, p);
}
JsonDeserializer valueDes = _valueDeserializer;
final TypeDeserializer typeDeser = _valueTypeDeserializer;
Object value;
try {
if (p.hasToken(JsonToken.VALUE_NULL)) {
// 03-Feb-2017, tatu: Hmmh. I wonder... let's try skipping here, too
if (_skipNullValues) {
return result;
}
value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
}
if (value == null) {
_tryToAddNull(p, ctxt, result);
return result;
}
} catch (Exception e) {
boolean wrap = ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
if (!wrap) {
ClassUtil.throwIfRTE(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;
}
protected Collection _deserializeWithObjectId(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.assignCurrentValue(result);
final JsonDeserializer valueDes = _valueDeserializer;
final TypeDeserializer typeDeser = _valueTypeDeserializer;
CollectionReferringAccumulator referringAccumulator =
new CollectionReferringAccumulator(_containerType.getContentType().getRawClass(), result);
JsonToken t;
while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
try {
Object value;
if (t == JsonToken.VALUE_NULL) {
if (_skipNullValues) {
continue;
}
value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
}
if (value == null && _skipNullValues) {
continue;
}
referringAccumulator.add(value);
} catch (UnresolvedForwardReference reference) {
Referring ref = referringAccumulator.handleUnresolvedReference(reference);
reference.getRoid().appendReferring(ref);
} catch (Exception e) {
boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
if (!wrap) {
ClassUtil.throwIfRTE(e);
}
throw JsonMappingException.wrapWithPath(e, result, result.size());
}
}
return result;
}
/**
* {@code java.util.TreeSet} (and possibly other {@link Collection} types) does not
* allow addition of {@code null} values, so isolate handling here.
*
* @since 2.17
*/
protected void _tryToAddNull(JsonParser p, DeserializationContext ctxt, Collection> set)
throws IOException
{
if (_skipNullValues) {
return;
}
// Ideally we'd have better idea of where nulls are accepted, but first
// let's just produce something better than NPE:
try {
set.add(null);
} catch (NullPointerException e) {
ctxt.handleUnexpectedToken(_valueType, JsonToken.VALUE_NULL, p,
"`java.util.Collection` of type %s does not accept `null` values",
ClassUtil.getTypeDescription(getValueType(ctxt)));
}
}
/**
* Helper class for dealing with Object Id references for values contained in
* collections being deserialized.
*/
public 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 {@code #id} parameter from {@link #handleResolvedForwardReference(Object, Object)}
* 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);
}
}
}