implements ContextualDeserializer, ResolvableDeserializer,
java.io.Serializable // since 2.1
{
private static final long serialVersionUID = 2960120955735322578L;
protected final static PropertyName TEMP_PROPERTY_NAME = new PropertyName("#temporary-name");
/*
/**********************************************************
/* Information regarding type being deserialized
/**********************************************************
*/
/**
* Annotations from the bean class: used for accessing
* annotations during resolution
* (see {@link #resolve}) and
* contextualization (see {@link #createContextual})
*
* Transient since annotations only used during construction.
*/
final private transient Annotations _classAnnotations;
/**
* Declared type of the bean this deserializer handles.
*/
final protected JavaType _beanType;
/**
* Requested shape from bean class annotations.
*/
final protected JsonFormat.Shape _serializationShape;
/*
/**********************************************************
/* Configuration for creating value instance
/**********************************************************
*/
/**
* Object that handles details of constructing initial
* bean value (to which bind data to), unless instance
* is passed (via updateValue())
*/
protected final ValueInstantiator _valueInstantiator;
/**
* Deserializer that is used iff delegate-based creator is
* to be used for deserializing from JSON Object.
*/
protected JsonDeserializer _delegateDeserializer;
/**
* If the bean needs to be instantiated using constructor
* or factory method
* that takes one or more named properties as argument(s),
* this creator is used for instantiation.
* This value gets resolved during general resolution.
*/
protected PropertyBasedCreator _propertyBasedCreator;
/**
* Flag that is set to mark "non-standard" cases; where either
* we use one of non-default creators, or there are unwrapped
* values to consider.
*/
protected boolean _nonStandardCreation;
/**
* Flag that indicates that no "special features" whatsoever
* are enabled, so the simplest processing is possible.
*/
protected boolean _vanillaProcessing;
/*
/**********************************************************
/* Property information, setters
/**********************************************************
*/
/**
* Mapping of property names to properties, built when all properties
* to use have been successfully resolved.
*/
final protected BeanPropertyMap _beanProperties;
/**
* List of {@link ValueInjector}s, if any injectable values are
* expected by the bean; otherwise null.
* This includes injectors used for injecting values via setters
* and fields, but not ones passed through constructor parameters.
*/
final protected ValueInjector[] _injectables;
/**
* Fallback setter used for handling any properties that are not
* mapped to regular setters. If setter is not null, it will be
* called once for each such property.
*/
protected SettableAnyProperty _anySetter;
/**
* In addition to properties that are set, we will also keep
* track of recognized but ignorable properties: these will
* be skipped without errors or warnings.
*/
final protected HashSet _ignorableProps;
/**
* Flag that can be set to ignore and skip unknown properties.
* If set, will not throw an exception for unknown properties.
*/
final protected boolean _ignoreAllUnknown;
/**
* Flag that indicates that some aspect of deserialization depends
* on active view used (if any)
*/
final protected boolean _needViewProcesing;
/**
* We may also have one or more back reference fields (usually
* zero or one).
*/
final protected Map _backRefs;
/*
/**********************************************************
/* Related handlers
/**********************************************************
*/
/**
* Lazily constructed map used to contain deserializers needed
* for polymorphic subtypes.
* Note that this is only needed for polymorphic types,
* that is, when the actual type is not statically known.
* For other types this remains null.
*/
protected transient HashMap> _subDeserializers;
/**
* If one of properties has "unwrapped" value, we need separate
* helper object
*/
protected UnwrappedPropertyHandler _unwrappedPropertyHandler;
/**
* Handler that we need iff any of properties uses external
* type id.
*/
protected ExternalTypeHandler _externalTypeIdHandler;
/**
* If an Object Id is to be used for value handled by this
* deserializer, this reader is used for handling.
*/
protected final ObjectIdReader _objectIdReader;
/*
/**********************************************************
/* Life-cycle, construction, initialization
/**********************************************************
*/
/**
* Constructor used when initially building a deserializer
* instance, given a {@link BeanDeserializerBuilder} that
* contains configuration.
*/
protected BeanDeserializerBase(BeanDeserializerBuilder builder,
BeanDescription beanDesc,
BeanPropertyMap properties, Map backRefs,
HashSet ignorableProps, boolean ignoreAllUnknown,
boolean hasViews)
{
super(beanDesc.getType());
AnnotatedClass ac = beanDesc.getClassInfo();
_classAnnotations = ac.getAnnotations();
_beanType = beanDesc.getType();
_valueInstantiator = builder.getValueInstantiator();
_beanProperties = properties;
_backRefs = backRefs;
_ignorableProps = ignorableProps;
_ignoreAllUnknown = ignoreAllUnknown;
_anySetter = builder.getAnySetter();
List injectables = builder.getInjectables();
_injectables = (injectables == null || injectables.isEmpty()) ? null
: injectables.toArray(new ValueInjector[injectables.size()]);
_objectIdReader = builder.getObjectIdReader();
_nonStandardCreation = (_unwrappedPropertyHandler != null)
|| _valueInstantiator.canCreateUsingDelegate()
|| _valueInstantiator.canCreateFromObjectWith()
|| !_valueInstantiator.canCreateUsingDefault()
;
// Any transformation we may need to apply?
JsonFormat.Value format = beanDesc.findExpectedFormat(null);
_serializationShape = (format == null) ? null : format.getShape();
_needViewProcesing = hasViews;
_vanillaProcessing = !_nonStandardCreation
&& (_injectables == null)
&& !_needViewProcesing
// also, may need to reorder stuff if we expect Object Id:
&& (_objectIdReader == null)
;
}
protected BeanDeserializerBase(BeanDeserializerBase src)
{
this(src, src._ignoreAllUnknown);
}
protected BeanDeserializerBase(BeanDeserializerBase src, boolean ignoreAllUnknown)
{
super(src._beanType);
_classAnnotations = src._classAnnotations;
_beanType = src._beanType;
_valueInstantiator = src._valueInstantiator;
_delegateDeserializer = src._delegateDeserializer;
_propertyBasedCreator = src._propertyBasedCreator;
_beanProperties = src._beanProperties;
_backRefs = src._backRefs;
_ignorableProps = src._ignorableProps;
_ignoreAllUnknown = ignoreAllUnknown;
_anySetter = src._anySetter;
_injectables = src._injectables;
_objectIdReader = src._objectIdReader;
_nonStandardCreation = src._nonStandardCreation;
_unwrappedPropertyHandler = src._unwrappedPropertyHandler;
_needViewProcesing = src._needViewProcesing;
_serializationShape = src._serializationShape;
_vanillaProcessing = src._vanillaProcessing;
}
protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapper)
{
super(src._beanType);
_classAnnotations = src._classAnnotations;
_beanType = src._beanType;
_valueInstantiator = src._valueInstantiator;
_delegateDeserializer = src._delegateDeserializer;
_propertyBasedCreator = src._propertyBasedCreator;
_backRefs = src._backRefs;
_ignorableProps = src._ignorableProps;
_ignoreAllUnknown = (unwrapper != null) || src._ignoreAllUnknown;
_anySetter = src._anySetter;
_injectables = src._injectables;
_objectIdReader = src._objectIdReader;
_nonStandardCreation = src._nonStandardCreation;
UnwrappedPropertyHandler uph = src._unwrappedPropertyHandler;
if (unwrapper != null) {
// delegate further unwraps, if any
if (uph != null) { // got handler, delegate
uph = uph.renameAll(unwrapper);
}
// and handle direct unwrapping as well:
_beanProperties = src._beanProperties.renameAll(unwrapper);
} else {
_beanProperties = src._beanProperties;
}
_unwrappedPropertyHandler = uph;
_needViewProcesing = src._needViewProcesing;
_serializationShape = src._serializationShape;
// probably adds a twist, so:
_vanillaProcessing = false;
}
public BeanDeserializerBase(BeanDeserializerBase src, ObjectIdReader oir)
{
super(src._beanType);
_classAnnotations = src._classAnnotations;
_beanType = src._beanType;
_valueInstantiator = src._valueInstantiator;
_delegateDeserializer = src._delegateDeserializer;
_propertyBasedCreator = src._propertyBasedCreator;
_backRefs = src._backRefs;
_ignorableProps = src._ignorableProps;
_ignoreAllUnknown = src._ignoreAllUnknown;
_anySetter = src._anySetter;
_injectables = src._injectables;
_nonStandardCreation = src._nonStandardCreation;
_unwrappedPropertyHandler = src._unwrappedPropertyHandler;
_needViewProcesing = src._needViewProcesing;
_serializationShape = src._serializationShape;
// then actual changes:
_objectIdReader = oir;
if (oir == null) {
_beanProperties = src._beanProperties;
_vanillaProcessing = src._vanillaProcessing;
} else {
/* 18-Nov-2012, tatu: May or may not have annotations for id property;
* but no easy access. But hard to see id property being optional,
* so let's consider required at this point.
*/
ObjectIdValueProperty idProp = new ObjectIdValueProperty(oir, PropertyMetadata.STD_REQUIRED);
_beanProperties = src._beanProperties.withProperty(idProp);
_vanillaProcessing = false;
}
}
public BeanDeserializerBase(BeanDeserializerBase src, HashSet ignorableProps)
{
super(src._beanType);
_classAnnotations = src._classAnnotations;
_beanType = src._beanType;
_valueInstantiator = src._valueInstantiator;
_delegateDeserializer = src._delegateDeserializer;
_propertyBasedCreator = src._propertyBasedCreator;
_backRefs = src._backRefs;
_ignorableProps = ignorableProps;
_ignoreAllUnknown = src._ignoreAllUnknown;
_anySetter = src._anySetter;
_injectables = src._injectables;
_nonStandardCreation = src._nonStandardCreation;
_unwrappedPropertyHandler = src._unwrappedPropertyHandler;
_needViewProcesing = src._needViewProcesing;
_serializationShape = src._serializationShape;
_vanillaProcessing = src._vanillaProcessing;
_objectIdReader = src._objectIdReader;
_beanProperties = src._beanProperties;
}
@Override
public abstract JsonDeserializer unwrappingDeserializer(NameTransformer unwrapper);
public abstract BeanDeserializerBase withObjectIdReader(ObjectIdReader oir);
public abstract BeanDeserializerBase withIgnorableProperties(HashSet ignorableProps);
/**
* Fluent factory for creating a variant that can handle
* POJO output as a JSON Array. Implementations may ignore this request
* if no such input is possible.
*
* @since 2.1
*/
protected abstract BeanDeserializerBase asArrayDeserializer();
/*
/**********************************************************
/* Validation, post-processing
/**********************************************************
*/
/**
* Method called to finalize setup of this deserializer,
* after deserializer itself has been registered.
* This is needed to handle recursive and transitive dependencies.
*/
@Override
public void resolve(DeserializationContext ctxt)
throws JsonMappingException
{
ExternalTypeHandler.Builder extTypes = null;
// if ValueInstantiator can use "creator" approach, need to resolve it here...
if (_valueInstantiator.canCreateFromObjectWith()) {
SettableBeanProperty[] creatorProps = _valueInstantiator.getFromObjectArguments(ctxt.getConfig());
_propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, creatorProps);
// also: need to try to resolve 'external' type ids...
for (SettableBeanProperty prop : _propertyBasedCreator.properties()) {
if (prop.hasValueTypeDeserializer()) {
TypeDeserializer typeDeser = prop.getValueTypeDeserializer();
if (typeDeser.getTypeInclusion() == JsonTypeInfo.As.EXTERNAL_PROPERTY) {
if (extTypes == null) {
extTypes = new ExternalTypeHandler.Builder();
}
extTypes.addExternal(prop, typeDeser);
}
}
}
}
UnwrappedPropertyHandler unwrapped = null;
for (SettableBeanProperty origProp : _beanProperties) {
SettableBeanProperty prop = origProp;
// May already have deserializer from annotations, if so, skip:
if (!prop.hasValueDeserializer()) {
// [Issue#125]: allow use of converters
JsonDeserializer> deser = findConvertingDeserializer(ctxt, prop);
if (deser == null) {
deser = findDeserializer(ctxt, prop.getType(), prop);
}
prop = prop.withValueDeserializer(deser);
} else { // may need contextual version
JsonDeserializer deser = prop.getValueDeserializer();
/* Important! This is the only place where actually handle "primary"
* property deserializers -- call is different from other places.
*/
JsonDeserializer> cd = ctxt.handlePrimaryContextualization(deser, prop);
if (cd != deser) {
prop = prop.withValueDeserializer(cd);
}
}
// [JACKSON-235]: need to link managed references with matching back references
prop = _resolveManagedReferenceProperty(ctxt, prop);
// [JACKSON-132]: support unwrapped values (via @JsonUnwrapped)
SettableBeanProperty u = _resolveUnwrappedProperty(ctxt, prop);
if (u != null) {
prop = u;
if (unwrapped == null) {
unwrapped = new UnwrappedPropertyHandler();
}
unwrapped.addProperty(prop);
continue;
}
// [JACKSON-594]: non-static inner classes too:
prop = _resolveInnerClassValuedProperty(ctxt, prop);
if (prop != origProp) {
_beanProperties.replace(prop);
}
/* one more thing: if this property uses "external property" type inclusion
* (see [JACKSON-453]), it needs different handling altogether
*/
if (prop.hasValueTypeDeserializer()) {
TypeDeserializer typeDeser = prop.getValueTypeDeserializer();
if (typeDeser.getTypeInclusion() == JsonTypeInfo.As.EXTERNAL_PROPERTY) {
if (extTypes == null) {
extTypes = new ExternalTypeHandler.Builder();
}
extTypes.addExternal(prop, typeDeser);
// In fact, remove from list of known properties to simplify later handling
_beanProperties.remove(prop);
continue;
}
}
}
// "any setter" may also need to be resolved now
if (_anySetter != null && !_anySetter.hasValueDeserializer()) {
_anySetter = _anySetter.withValueDeserializer(findDeserializer(ctxt,
_anySetter.getType(), _anySetter.getProperty()));
}
// as well as delegate-based constructor:
if (_valueInstantiator.canCreateUsingDelegate()) {
JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig());
if (delegateType == null) {
throw new IllegalArgumentException("Invalid delegate-creator definition for "+_beanType
+": value instantiator ("+_valueInstantiator.getClass().getName()
+") returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'");
}
AnnotatedWithParams delegateCreator = _valueInstantiator.getDelegateCreator();
// Need to create a temporary property to allow contextual deserializers:
BeanProperty.Std property = new BeanProperty.Std(TEMP_PROPERTY_NAME,
delegateType, null, _classAnnotations, delegateCreator,
PropertyMetadata.STD_OPTIONAL);
_delegateDeserializer = findDeserializer(ctxt, delegateType, property);
}
if (extTypes != null) {
_externalTypeIdHandler = extTypes.build();
// we consider this non-standard, to offline handling
_nonStandardCreation = true;
}
_unwrappedPropertyHandler = unwrapped;
if (unwrapped != null) { // we consider this non-standard, to offline handling
_nonStandardCreation = true;
}
// may need to disable vanilla processing, if unwrapped handling was enabled...
_vanillaProcessing = _vanillaProcessing && !_nonStandardCreation;
}
/**
* Helper method that can be used to see if specified property is annotated
* to indicate use of a converter for property value (in case of container types,
* it is container type itself, not key or content type).
*
* @since 2.2
*/
protected JsonDeserializer findConvertingDeserializer(DeserializationContext ctxt,
SettableBeanProperty prop)
throws JsonMappingException
{
final AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
if (intr != null) {
Object convDef = intr.findDeserializationConverter(prop.getMember());
if (convDef != null) {
Converter conv = ctxt.converterInstance(prop.getMember(), convDef);
JavaType delegateType = conv.getInputType(ctxt.getTypeFactory());
JsonDeserializer> ser = ctxt.findContextualValueDeserializer(delegateType, prop);
return new StdDelegatingDeserializer(conv, delegateType, ser);
}
}
return null;
}
/**
* Although most of post-processing is done in resolve(), we only get
* access to referring property's annotations here; and this is needed
* to support per-property ObjectIds.
* We will also consider Shape transformations (read from Array) at this
* point, since it may come from either Class definition or property.
*/
@Override
public JsonDeserializer> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
ObjectIdReader oir = _objectIdReader;
String[] ignorals = null;
// First: may have an override for Object Id:
final AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
final AnnotatedMember accessor = (property == null || intr == null)
? null : property.getMember();
if (property != null && intr != null) {
ignorals = intr.findPropertiesToIgnore(accessor);
ObjectIdInfo objectIdInfo = intr.findObjectIdInfo(accessor);
if (objectIdInfo != null) { // some code duplication here as well (from BeanDeserializerFactory)
// 2.1: allow modifications by "id ref" annotations as well:
objectIdInfo = intr.findObjectReferenceInfo(accessor, objectIdInfo);
Class> implClass = objectIdInfo.getGeneratorType();
// Property-based generator is trickier
JavaType idType;
SettableBeanProperty idProp;
ObjectIdGenerator> idGen;
if (implClass == ObjectIdGenerators.PropertyGenerator.class) {
PropertyName propName = objectIdInfo.getPropertyName();
idProp = findProperty(propName);
if (idProp == null) {
throw new IllegalArgumentException("Invalid Object Id definition for "
+handledType().getName()+": can not find property with name '"+propName+"'");
}
idType = idProp.getType();
idGen = new PropertyBasedObjectIdGenerator(objectIdInfo.getScope());
} else { // other types need to be simpler
JavaType type = ctxt.constructType(implClass);
idType = ctxt.getTypeFactory().findTypeParameters(type, ObjectIdGenerator.class)[0];
idProp = null;
idGen = ctxt.objectIdGeneratorInstance(accessor, objectIdInfo);
}
JsonDeserializer> deser = ctxt.findRootValueDeserializer(idType);
oir = ObjectIdReader.construct(idType, objectIdInfo.getPropertyName(),
idGen, deser, idProp);
}
}
// either way, need to resolve serializer:
BeanDeserializerBase contextual = this;
if (oir != null && oir != _objectIdReader) {
contextual = contextual.withObjectIdReader(oir);
}
// And possibly add more properties to ignore
if (ignorals != null && ignorals.length != 0) {
HashSet newIgnored = ArrayBuilders.setAndArray(contextual._ignorableProps, ignorals);
contextual = contextual.withIgnorableProperties(newIgnored);
}
// One more thing: are we asked to serialize POJO as array?
JsonFormat.Shape shape = null;
if (accessor != null) {
JsonFormat.Value format = intr.findFormat((Annotated) accessor);
if (format != null) {
shape = format.getShape();
}
}
if (shape == null) {
shape = _serializationShape;
}
if (shape == JsonFormat.Shape.ARRAY) {
contextual = contextual.asArrayDeserializer();
}
return contextual;
}
/**
* Helper method called to see if given property is part of 'managed' property
* pair (managed + back reference), and if so, handle resolution details.
*/
protected SettableBeanProperty _resolveManagedReferenceProperty(DeserializationContext ctxt,
SettableBeanProperty prop)
{
String refName = prop.getManagedReferenceName();
if (refName == null) {
return prop;
}
JsonDeserializer> valueDeser = prop.getValueDeserializer();
SettableBeanProperty backProp = valueDeser.findBackReference(refName);
if (backProp == null) {
throw new IllegalArgumentException("Can not handle managed/back reference '"+refName+"': no back reference property found from type "
+prop.getType());
}
// also: verify that type is compatible
JavaType referredType = _beanType;
JavaType backRefType = backProp.getType();
boolean isContainer = prop.getType().isContainerType();
if (!backRefType.getRawClass().isAssignableFrom(referredType.getRawClass())) {
throw new IllegalArgumentException("Can not handle managed/back reference '"+refName+"': back reference type ("
+backRefType.getRawClass().getName()+") not compatible with managed type ("
+referredType.getRawClass().getName()+")");
}
return new ManagedReferenceProperty(prop, refName, backProp,
_classAnnotations, isContainer);
}
/**
* Helper method called to see if given property might be so-called unwrapped
* property: these require special handling.
*/
protected SettableBeanProperty _resolveUnwrappedProperty(DeserializationContext ctxt,
SettableBeanProperty prop)
{
AnnotatedMember am = prop.getMember();
if (am != null) {
NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(am);
if (unwrapper != null) {
JsonDeserializer orig = prop.getValueDeserializer();
JsonDeserializer unwrapping = orig.unwrappingDeserializer(unwrapper);
if (unwrapping != orig && unwrapping != null) {
// might be cleaner to create new instance; but difficult to do reliably, so:
return prop.withValueDeserializer(unwrapping);
}
}
}
return null;
}
/**
* Helper method that will handle gruesome details of dealing with properties
* that have non-static inner class as value...
*/
protected SettableBeanProperty _resolveInnerClassValuedProperty(DeserializationContext ctxt,
SettableBeanProperty prop)
{
/* Should we encounter a property that has non-static inner-class
* as value, we need to add some more magic to find the "hidden" constructor...
*/
JsonDeserializer deser = prop.getValueDeserializer();
// ideally wouldn't rely on it being BeanDeserializerBase; but for now it'll have to do
if (deser instanceof BeanDeserializerBase) {
BeanDeserializerBase bd = (BeanDeserializerBase) deser;
ValueInstantiator vi = bd.getValueInstantiator();
if (!vi.canCreateUsingDefault()) { // no default constructor
Class> valueClass = prop.getType().getRawClass();
Class> enclosing = ClassUtil.getOuterClass(valueClass);
// and is inner class of the bean class...
if (enclosing != null && enclosing == _beanType.getRawClass()) {
for (Constructor> ctor : valueClass.getConstructors()) {
Class>[] paramTypes = ctor.getParameterTypes();
if (paramTypes.length == 1 && paramTypes[0] == enclosing) {
if (ctxt.getConfig().canOverrideAccessModifiers()) {
ClassUtil.checkAndFixAccess(ctor);
}
return new InnerClassProperty(prop, ctor);
}
}
}
}
}
return prop;
}
/*
/**********************************************************
/* Public accessors
/**********************************************************
*/
@Override
public boolean isCachable() { return true; }
@Override
public Class> handledType() {
return _beanType.getRawClass();
}
/**
* Overridden to return true for those instances that are
* handling value for which Object Identity handling is enabled
* (either via value type or referring property).
*/
@Override
public ObjectIdReader getObjectIdReader() {
return _objectIdReader;
}
public boolean hasProperty(String propertyName) {
return _beanProperties.find(propertyName) != null;
}
public boolean hasViews() {
return _needViewProcesing;
}
/**
* Accessor for checking number of deserialized properties.
*/
public int getPropertyCount() {
return _beanProperties.size();
}
@Override
public Collection getKnownPropertyNames() {
ArrayList names = new ArrayList();
for (SettableBeanProperty prop : _beanProperties) {
names.add(prop.getName());
}
return names;
}
/**
* @deprecated Since 2.3, use {@link #handledType()} instead
*/
@Deprecated
public final Class> getBeanClass() { return _beanType.getRawClass(); }
@Override
public JavaType getValueType() { return _beanType; }
/**
* Accessor for iterating over properties this deserializer uses; with
* the exception that properties passed via Creator methods
* (specifically, "property-based constructor") are not included,
* but can be accessed separate by calling
* {@link #creatorProperties}
*/
public Iterator properties()
{
if (_beanProperties == null) {
throw new IllegalStateException("Can only call after BeanDeserializer has been resolved");
}
return _beanProperties.iterator();
}
/**
* Accessor for finding properties that represents values to pass
* through property-based creator method (constructor or
* factory method)
*
* @since 2.0
*/
public Iterator creatorProperties()
{
if (_propertyBasedCreator == null) {
return Collections.emptyList().iterator();
}
return _propertyBasedCreator.properties().iterator();
}
public SettableBeanProperty findProperty(PropertyName propertyName)
{
// TODO: start matching full name?
return findProperty(propertyName.getSimpleName());
}
/**
* Accessor for finding the property with given name, if POJO
* has one. Name used is the external name, i.e. name used
* in external data representation (JSON).
*
* @since 2.0
*/
public SettableBeanProperty findProperty(String propertyName)
{
SettableBeanProperty prop = (_beanProperties == null) ?
null : _beanProperties.find(propertyName);
if (prop == null && _propertyBasedCreator != null) {
prop = _propertyBasedCreator.findCreatorProperty(propertyName);
}
return prop;
}
/**
* Alternate find method that tries to locate a property with given
* property index
.
* Note that access by index is not necessarily faster than by name,
* since properties are not directly indexable; however, for most
* instances difference is not significant as number of properties
* is low.
*
* @since 2.3
*/
public SettableBeanProperty findProperty(int propertyIndex)
{
SettableBeanProperty prop = (_beanProperties == null) ?
null : _beanProperties.find(propertyIndex);
if (prop == null && _propertyBasedCreator != null) {
prop = _propertyBasedCreator.findCreatorProperty(propertyIndex);
}
return prop;
}
/**
* Method needed by {@link BeanDeserializerFactory} to properly link
* managed- and back-reference pairs.
*/
@Override
public SettableBeanProperty findBackReference(String logicalName)
{
if (_backRefs == null) {
return null;
}
return _backRefs.get(logicalName);
}
public ValueInstantiator getValueInstantiator() {
return _valueInstantiator;
}
/*
/**********************************************************
/* Mutators
/**********************************************************
*/
/**
* Method that can be used to replace an existing property with
* a modified one.
*
* NOTE: only ever use this method if you know what you are doing;
* incorrect usage can break deserializer.
*
* @param original Property to replace
* @param replacement Property to replace it with
*
* @since 2.1
*/
public void replaceProperty(SettableBeanProperty original,
SettableBeanProperty replacement)
{
_beanProperties.replace(replacement);
}
/*
/**********************************************************
/* Partial deserializer implementation
/**********************************************************
*/
/**
* General version used when handling needs more advanced
* features.
*/
public abstract Object deserializeFromObject(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException;
@Override
public final Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException
{
// 16-Feb-2012, tatu: ObjectId may be used as well... need to check that first
if (_objectIdReader != null) {
// 05-Aug-2013, tatu: May use native Object Id
if (jp.canReadObjectId()) {
Object id = jp.getObjectId();
if (id != null) {
Object ob = typeDeserializer.deserializeTypedFromObject(jp, ctxt);
return _handleTypedObjectId(jp, ctxt, ob, id);
}
}
// or, Object Ids Jackson explicitly sets
JsonToken t = jp.getCurrentToken();
// for now (2.2.x) we only allow scalar types (Strings, integral numbers):
// NOTE: may need to allow handling of structured values in future for JSOG
if (t != null && t.isScalarValue()) {
return deserializeFromObjectId(jp, ctxt);
}
}
// In future could check current token... for now this should be enough:
return typeDeserializer.deserializeTypedFromObject(jp, ctxt);
}
/**
* Offlined method called to handle "native" Object Id that has been read
* and known to be associated with given deserialized POJO.
*
* @since 2.3
*/
protected Object _handleTypedObjectId(JsonParser jp, DeserializationContext ctxt,
Object pojo, Object rawId)
throws IOException, JsonProcessingException
{
/* 07-Aug-2013, tatu: One more challenge: type of id may not be type
* of property we are expecting later on; specifically, numeric ids
* vs Strings.
*/
JsonDeserializer idDeser = _objectIdReader.getDeserializer();
final Object id;
// Ok, this is bit ridiculous; let's see if conversion is needed:
if (idDeser.handledType() == rawId.getClass()) {
// nope: already same type
id = rawId;
} else {
id = _convertObjectId(jp, ctxt, rawId, idDeser);
}
ReadableObjectId roid = ctxt.findObjectId(id, _objectIdReader.generator);
roid.bindItem(pojo);
// also: may need to set a property value as well
SettableBeanProperty idProp = _objectIdReader.idProperty;
if (idProp != null) {
return idProp.setAndReturn(pojo, id);
}
return pojo;
}
/**
* Helper method we need to do necessary conversion from whatever native object id
* type is, into declared type that Jackson internals expect. This may be
* simple cast (for String ids), or something more complicated; in latter
* case we may need to create bogus content buffer to allow use of
* id deserializer.
*
* @since 2.3
*/
@SuppressWarnings("resource") // TokenBuffers don't need close, nor parser thereof
protected Object _convertObjectId(JsonParser jp, DeserializationContext ctxt,
Object rawId, JsonDeserializer idDeser)
throws IOException, JsonProcessingException
{
TokenBuffer buf = new TokenBuffer(jp);
if (rawId instanceof String) {
buf.writeString((String) rawId);
} else if (rawId instanceof Long) {
buf.writeNumber(((Long) rawId).longValue());
} else if (rawId instanceof Integer) {
buf.writeNumber(((Integer) rawId).intValue());
} else {
// should we worry about UUIDs? They should be fine, right?
buf.writeObject(rawId);
}
JsonParser bufParser = buf.asParser();
bufParser.nextToken();
return idDeser.deserialize(bufParser, ctxt);
}
// NOTE: currently only used by standard BeanDeserializer (not Builder-based)
/**
* Alternative deserialization method used when we expect to see Object Id;
* if so, we will need to ensure that the Id is seen before anything
* else, to ensure that it is available for solving references,
* even if JSON itself is not ordered that way. This may require
* buffering in some cases, but usually just a simple lookup to ensure
* that ordering is correct.
*/
@SuppressWarnings("resource")
protected Object deserializeWithObjectId(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
final String idPropName = _objectIdReader.propertyName.getSimpleName();
// First, the simple case: we point to the Object Id property
if (idPropName.equals(jp.getCurrentName())
// 05-Aug-2013, tatu: Or might point to a native Object Id
|| jp.canReadObjectId()) {
return deserializeFromObject(jp, ctxt);
}
// otherwise need to reorder things
TokenBuffer tmpBuffer = new TokenBuffer(jp);
TokenBuffer mergedBuffer = null;
for (; jp.getCurrentToken() != JsonToken.END_OBJECT; jp.nextToken()) {
String propName = jp.getCurrentName();
// when we match the id property, can start merging
if (mergedBuffer == null) {
if (idPropName.equals(propName)) {
mergedBuffer = new TokenBuffer(jp);
mergedBuffer.writeFieldName(propName);
jp.nextToken();
mergedBuffer.copyCurrentStructure(jp);
mergedBuffer.append(tmpBuffer);
tmpBuffer = null;
} else {
tmpBuffer.writeFieldName(propName);
jp.nextToken();
tmpBuffer.copyCurrentStructure(jp);
}
} else {
mergedBuffer.writeFieldName(propName);
jp.nextToken();
mergedBuffer.copyCurrentStructure(jp);
}
}
// note: we really should get merged buffer (and if not, that is likely error), but
// for now let's allow missing case as well. Will be caught be a later stage...
TokenBuffer buffer = (mergedBuffer == null) ? tmpBuffer : mergedBuffer;
buffer.writeEndObject();
// important: need to advance to point to first FIELD_NAME:
JsonParser mergedParser = buffer.asParser();
mergedParser.nextToken();
return deserializeFromObject(mergedParser, ctxt);
}
/**
* Method called in cases where it looks like we got an Object Id
* to parse and use as a reference.
*/
protected Object deserializeFromObjectId(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
Object id = _objectIdReader.readObjectReference(jp, ctxt);
ReadableObjectId roid = ctxt.findObjectId(id, _objectIdReader.generator);
// do we have it resolved?
Object pojo = roid.item;
if (pojo == null) { // not yet; should wait...
throw new IllegalStateException("Could not resolve Object Id ["+id+"] (for "
+_beanType+") -- unresolved forward-reference?");
}
return pojo;
}
protected Object deserializeFromObjectUsingNonDefault(JsonParser jp,
DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (_delegateDeserializer != null) {
return _valueInstantiator.createUsingDelegate(ctxt,
_delegateDeserializer.deserialize(jp, ctxt));
}
if (_propertyBasedCreator != null) {
return _deserializeUsingPropertyBased(jp, ctxt);
}
// should only occur for abstract types...
if (_beanType.isAbstract()) {
throw JsonMappingException.from(jp, "Can not instantiate abstract type "+_beanType
+" (need to add/enable type information?)");
}
throw JsonMappingException.from(jp, "No suitable constructor found for type "
+_beanType+": can not instantiate from JSON object (need to add/enable type information?)");
}
protected abstract Object _deserializeUsingPropertyBased(final JsonParser jp,
final DeserializationContext ctxt)
throws IOException, JsonProcessingException;
@SuppressWarnings("incomplete-switch")
public Object deserializeFromNumber(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// First things first: id Object Id is used, most likely that's it
if (_objectIdReader != null) {
return deserializeFromObjectId(jp, ctxt);
}
switch (jp.getNumberType()) {
case INT:
if (_delegateDeserializer != null) {
if (!_valueInstantiator.canCreateFromInt()) {
Object bean = _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt));
if (_injectables != null) {
injectValues(ctxt, bean);
}
return bean;
}
}
return _valueInstantiator.createFromInt(ctxt, jp.getIntValue());
case LONG:
if (_delegateDeserializer != null) {
if (!_valueInstantiator.canCreateFromInt()) {
Object bean = _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt));
if (_injectables != null) {
injectValues(ctxt, bean);
}
return bean;
}
}
return _valueInstantiator.createFromLong(ctxt, jp.getLongValue());
}
// actually, could also be BigInteger, so:
if (_delegateDeserializer != null) {
Object bean = _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt));
if (_injectables != null) {
injectValues(ctxt, bean);
}
return bean;
}
throw ctxt.instantiationException(getBeanClass(), "no suitable creator method found to deserialize from JSON integer number");
}
public Object deserializeFromString(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// First things first: id Object Id is used, most likely that's it
if (_objectIdReader != null) {
return deserializeFromObjectId(jp, ctxt);
}
/* Bit complicated if we have delegating creator; may need to use it,
* or might not...
*/
if (_delegateDeserializer != null) {
if (!_valueInstantiator.canCreateFromString()) {
Object bean = _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt));
if (_injectables != null) {
injectValues(ctxt, bean);
}
return bean;
}
}
return _valueInstantiator.createFromString(ctxt, jp.getText());
}
/**
* Method called to deserialize POJO value from a JSON floating-point
* number.
*/
@SuppressWarnings("incomplete-switch")
public Object deserializeFromDouble(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
switch (jp.getNumberType()) {
case FLOAT: // no separate methods for taking float...
case DOUBLE:
if (_delegateDeserializer != null) {
if (!_valueInstantiator.canCreateFromDouble()) {
Object bean = _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt));
if (_injectables != null) {
injectValues(ctxt, bean);
}
return bean;
}
}
return _valueInstantiator.createFromDouble(ctxt, jp.getDoubleValue());
}
// actually, could also be BigDecimal, so:
if (_delegateDeserializer != null) {
return _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt));
}
throw ctxt.instantiationException(getBeanClass(), "no suitable creator method found to deserialize from JSON floating-point number");
}
/**
* Method called to deserialize POJO value from a JSON boolean value (true, false)
*/
public Object deserializeFromBoolean(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (_delegateDeserializer != null) {
if (!_valueInstantiator.canCreateFromBoolean()) {
Object bean = _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt));
if (_injectables != null) {
injectValues(ctxt, bean);
}
return bean;
}
}
boolean value = (jp.getCurrentToken() == JsonToken.VALUE_TRUE);
return _valueInstantiator.createFromBoolean(ctxt, value);
}
public Object deserializeFromArray(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (_delegateDeserializer != null) {
try {
Object bean = _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(jp, ctxt));
if (_injectables != null) {
injectValues(ctxt, bean);
}
return bean;
} catch (Exception e) {
wrapInstantiationProblem(e, ctxt);
}
}
throw ctxt.mappingException(getBeanClass());
}
/*
/**********************************************************
/* Overridable helper methods
/**********************************************************
*/
protected void injectValues(DeserializationContext ctxt, Object bean)
throws IOException, JsonProcessingException
{
for (ValueInjector injector : _injectables) {
injector.inject(ctxt, bean);
}
}
/**
* Method called to handle set of one or more unknown properties,
* stored in their entirety in given {@link TokenBuffer}
* (as field entries, name and value).
*/
@SuppressWarnings("resource")
protected Object handleUnknownProperties(DeserializationContext ctxt,
Object bean, TokenBuffer unknownTokens)
throws IOException, JsonProcessingException
{
// First: add closing END_OBJECT as marker
unknownTokens.writeEndObject();
// note: buffer does NOT have starting START_OBJECT
JsonParser bufferParser = unknownTokens.asParser();
while (bufferParser.nextToken() != JsonToken.END_OBJECT) {
String propName = bufferParser.getCurrentName();
// Unknown: let's call handler method
bufferParser.nextToken();
handleUnknownProperty(bufferParser, ctxt, bean, propName);
}
return bean;
}
/**
* Helper method called for an unknown property, when using "vanilla"
* processing.
*/
protected void handleUnknownVanilla(JsonParser jp, DeserializationContext ctxt,
Object bean, String propName)
throws IOException, JsonProcessingException
{
if (_ignorableProps != null && _ignorableProps.contains(propName)) {
handleIgnoredProperty(jp, ctxt, bean, propName);
} else if (_anySetter != null) {
try {
// should we consider return type of any setter?
_anySetter.deserializeAndSet(jp, ctxt, bean, propName);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
} else {
// Unknown: let's call handler method
handleUnknownProperty(jp, ctxt, bean, propName);
}
}
/**
* Method called when a JSON property is encountered that has not matching
* setter, any-setter or field, and thus can not be assigned.
*/
@Override
protected void handleUnknownProperty(JsonParser jp, DeserializationContext ctxt,
Object beanOrClass, String propName)
throws IOException, JsonProcessingException
{
if (_ignoreAllUnknown) {
jp.skipChildren();
return;
}
if (_ignorableProps != null && _ignorableProps.contains(propName)) {
handleIgnoredProperty(jp, ctxt, beanOrClass, propName);
}
// Otherwise use default handling (call handler(s); if not
// handled, throw exception or skip depending on settings)
super.handleUnknownProperty(jp, ctxt, beanOrClass, propName);
}
/**
* Method called when an explicitly ignored property (one specified with a
* name to match, either by property annotation or class annotation) is encountered.
*
* @since 2.3
*/
protected void handleIgnoredProperty(JsonParser jp, DeserializationContext ctxt,
Object beanOrClass, String propName)
throws IOException, JsonProcessingException
{
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES)) {
throw IgnoredPropertyException.from(jp, beanOrClass, propName, getKnownPropertyNames());
}
jp.skipChildren();
}
/**
* Method called in cases where we may have polymorphic deserialization
* case: that is, type of Creator-constructed bean is not the type
* of deserializer itself. It should be a sub-class or implementation
* class; either way, we may have more specific deserializer to use
* for handling it.
*
* @param jp (optional) If not null, parser that has more properties to handle
* (in addition to buffered properties); if null, all properties are passed
* in buffer
*/
@SuppressWarnings("resource")
protected Object handlePolymorphic(JsonParser jp, DeserializationContext ctxt,
Object bean, TokenBuffer unknownTokens)
throws IOException, JsonProcessingException
{
// First things first: maybe there is a more specific deserializer available?
JsonDeserializer subDeser = _findSubclassDeserializer(ctxt, bean, unknownTokens);
if (subDeser != null) {
if (unknownTokens != null) {
// need to add END_OBJECT marker first
unknownTokens.writeEndObject();
JsonParser p2 = unknownTokens.asParser();
p2.nextToken(); // to get to first data field
bean = subDeser.deserialize(p2, ctxt, bean);
}
// Original parser may also have some leftovers
if (jp != null) {
bean = subDeser.deserialize(jp, ctxt, bean);
}
return bean;
}
// nope; need to use this deserializer. Unknowns we've seen so far?
if (unknownTokens != null) {
bean = handleUnknownProperties(ctxt, bean, unknownTokens);
}
// and/or things left to process via main parser?
if (jp != null) {
bean = deserialize(jp, ctxt, bean);
}
return bean;
}
/**
* Helper method called to (try to) locate deserializer for given sub-type of
* type that this deserializer handles.
*/
protected JsonDeserializer _findSubclassDeserializer(DeserializationContext ctxt,
Object bean, TokenBuffer unknownTokens)
throws IOException, JsonProcessingException
{
JsonDeserializer subDeser;
// First: maybe we have already created sub-type deserializer?
synchronized (this) {
subDeser = (_subDeserializers == null) ? null : _subDeserializers.get(new ClassKey(bean.getClass()));
}
if (subDeser != null) {
return subDeser;
}
// If not, maybe we can locate one. First, need provider
JavaType type = ctxt.constructType(bean.getClass());
/* 30-Jan-2012, tatu: Ideally we would be passing referring
* property; which in theory we could keep track of via
* ResolvableDeserializer (if we absolutely must...).
* But for now, let's not bother.
*/
// subDeser = ctxt.findValueDeserializer(type, _property);
subDeser = ctxt.findRootValueDeserializer(type);
// Also, need to cache it
if (subDeser != null) {
synchronized (this) {
if (_subDeserializers == null) {
_subDeserializers = new HashMap>();;
}
_subDeserializers.put(new ClassKey(bean.getClass()), subDeser);
}
}
return subDeser;
}
/*
/**********************************************************
/* Helper methods for error reporting
/**********************************************************
*/
/**
* Method that will modify caught exception (passed in as argument)
* as necessary to include reference information, and to ensure it
* is a subtype of {@link IOException}, or an unchecked exception.
*
* Rules for wrapping and unwrapping are bit complicated; essentially:
*
* Errors are to be passed as is (if uncovered via unwrapping)
* "Plain" IOExceptions (ones that are not of type
* {@link JsonMappingException} are to be passed as is
*
*/
public void wrapAndThrow(Throwable t, Object bean, String fieldName,
DeserializationContext ctxt)
throws IOException
{
// [JACKSON-55] Need to add reference information
throw JsonMappingException.wrapWithPath(throwOrReturnThrowable(t, ctxt), bean, fieldName);
}
public void wrapAndThrow(Throwable t, Object bean, int index, DeserializationContext ctxt)
throws IOException
{
// [JACKSON-55] Need to add reference information
throw JsonMappingException.wrapWithPath(throwOrReturnThrowable(t, ctxt), bean, index);
}
private Throwable throwOrReturnThrowable(Throwable t, DeserializationContext ctxt)
throws IOException
{
/* 05-Mar-2009, tatu: But one nasty edge is when we get
* StackOverflow: usually due to infinite loop. But that
* often gets hidden within an InvocationTargetException...
*/
while (t instanceof InvocationTargetException && t.getCause() != null) {
t = t.getCause();
}
// Errors to be passed as is
if (t instanceof Error) {
throw (Error) t;
}
boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
// Ditto for IOExceptions; except we may want to wrap JSON exceptions
if (t instanceof IOException) {
if (!wrap || !(t instanceof JsonProcessingException)) {
throw (IOException) t;
}
} else if (!wrap) { // [JACKSON-407] -- allow disabling wrapping for unchecked exceptions
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
}
return t;
}
protected void wrapInstantiationProblem(Throwable t, DeserializationContext ctxt)
throws IOException
{
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;
}
boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
if (t instanceof IOException) {
// Since we have no more information to add, let's not actually wrap..
throw (IOException) t;
} else if (!wrap) { // [JACKSON-407] -- allow disabling wrapping for unchecked exceptions
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
}
throw ctxt.instantiationException(_beanType.getRawClass(), t);
}
}