blob: 06488b19534c7ed4077bd1bfd69dc0227315b219 [file] [log] [blame]
package com.fasterxml.jackson.databind.deser.std;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator;
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.util.ClassUtil;
/**
* Deserializer that uses a single-String static factory method
* for locating Enum values by String id.
*
* @since 2.8 (as stand-alone class; was static inner class of {@link EnumDeserializer}
*/
class FactoryBasedEnumDeserializer
extends StdDeserializer<Object>
implements ContextualDeserializer
{
private static final long serialVersionUID = 1;
// Marker type; null if String expected; otherwise numeric wrapper
protected final JavaType _inputType;
protected final boolean _hasArgs;
protected final AnnotatedMethod _factory;
protected final JsonDeserializer<?> _deser;
protected final ValueInstantiator _valueInstantiator;
protected final SettableBeanProperty[] _creatorProps;
/**
* Lazily instantiated property-based creator.
*
* @since 2.8
*/
private transient PropertyBasedCreator _propCreator;
public FactoryBasedEnumDeserializer(Class<?> cls, AnnotatedMethod f, JavaType paramType,
ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps)
{
super(cls);
_factory = f;
_hasArgs = true;
// We'll skip case of `String`, as well as no type (zero-args):
_inputType = paramType.hasRawClass(String.class) ? null : paramType;
_deser = null;
_valueInstantiator = valueInstantiator;
_creatorProps = creatorProps;
}
/**
* @since 2.8
*/
public FactoryBasedEnumDeserializer(Class<?> cls, AnnotatedMethod f)
{
super(cls);
_factory = f;
_hasArgs = false;
_inputType = null;
_deser = null;
_valueInstantiator = null;
_creatorProps = null;
}
protected FactoryBasedEnumDeserializer(FactoryBasedEnumDeserializer base,
JsonDeserializer<?> deser) {
super(base._valueClass);
_inputType = base._inputType;
_factory = base._factory;
_hasArgs = base._hasArgs;
_valueInstantiator = base._valueInstantiator;
_creatorProps = base._creatorProps;
_deser = deser;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
throws JsonMappingException
{
if ((_deser == null) && (_inputType != null) && (_creatorProps == null)) {
return new FactoryBasedEnumDeserializer(this,
ctxt.findContextualValueDeserializer(_inputType, property));
}
return this;
}
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
return Boolean.FALSE;
}
@Override // since 2.12
public LogicalType logicalType() {
return LogicalType.Enum;
}
// since 2.9.7: should have been the case earlier but
@Override
public boolean isCachable() { return true; }
@Override
public ValueInstantiator getValueInstantiator() { return _valueInstantiator; }
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
Object value;
if (_deser != null) {
value = _deser.deserialize(p, ctxt);
} else if (_hasArgs) {
JsonToken curr = p.currentToken();
// 30-Mar-2020, tatu: For properties-based one, MUST get JSON Object (before
// 2.11, was just assuming match)
if (_creatorProps != null) {
if (!p.isExpectedStartObjectToken()) {
final JavaType targetType = getValueType(ctxt);
ctxt.reportInputMismatch(targetType,
"Input mismatch reading Enum %s: properties-based `@JsonCreator` (%s) expects JSON Object (JsonToken.START_OBJECT), got JsonToken.%s",
ClassUtil.getTypeDescription(targetType), _factory, p.currentToken());
}
if (_propCreator == null) {
_propCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, _creatorProps,
ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES));
}
p.nextToken();
return deserializeEnumUsingPropertyBased(p, ctxt, _propCreator);
}
// 30-Mar-2020, tatu: Single-arg delegating creators may go through
// here; although not 100% sure why they do not take the first branch
if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) {
value = p.getText();
} else if (curr == JsonToken.VALUE_NUMBER_INT) {
value = p.getNumberValue();
} else {
value = p.getValueAsString();
}
} else { // zero-args; just skip whatever value there may be
p.skipChildren();
try {
return _factory.call();
} catch (Exception e) {
Throwable t = ClassUtil.throwRootCauseIfIOE(e);
return ctxt.handleInstantiationProblem(_valueClass, null, t);
}
}
try {
return _factory.callOnWith(_valueClass, value);
} catch (Exception e) {
Throwable t = ClassUtil.throwRootCauseIfIOE(e);
// [databind#1642]:
if (ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL) &&
t instanceof IllegalArgumentException) {
return null;
}
return ctxt.handleInstantiationProblem(_valueClass, value, t);
}
}
@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
if (_deser == null) { // String never has type info
return deserialize(p, ctxt);
}
return typeDeserializer.deserializeTypedFromAny(p, ctxt);
}
// Method to deserialize the Enum using property based methodology
protected Object deserializeEnumUsingPropertyBased(final JsonParser p, final DeserializationContext ctxt,
final PropertyBasedCreator creator) throws IOException
{
PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, null);
JsonToken t = p.currentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken(); // to point to value
SettableBeanProperty creatorProp = creator.findCreatorProperty(propName);
if (creatorProp != null) {
buffer.assignParameter(creatorProp, _deserializeWithErrorWrapping(p, ctxt, creatorProp));
continue;
}
if (buffer.readIdProperty(propName)) {
continue;
}
}
return creator.build(ctxt, buffer);
}
// ************ Got the below methods from BeanDeserializer ********************//
protected final Object _deserializeWithErrorWrapping(JsonParser p, DeserializationContext ctxt,
SettableBeanProperty prop) throws IOException
{
try {
return prop.deserialize(p, ctxt);
} catch (Exception e) {
return wrapAndThrow(e, handledType(), prop.getName(), ctxt);
}
}
protected Object wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt)
throws IOException
{
throw JsonMappingException.wrapWithPath(throwOrReturnThrowable(t, ctxt), bean, fieldName);
}
private Throwable throwOrReturnThrowable(Throwable t, DeserializationContext ctxt) throws IOException
{
t = ClassUtil.getRootCause(t);
// Errors to be passed as is
ClassUtil.throwIfError(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) {
ClassUtil.throwIfRTE(t);
}
return t;
}
}