package com.fasterxml.jackson.databind.deser.std;
import java.io.IOException;
import java.util.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.JsonParser.NumberType;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
/**
* Base class for common deserializers. Contains shared
* base functionality for dealing with primitive values, such
* as (re)parsing from String.
*/
public abstract class StdDeserializer<T>
extends JsonDeserializer<T>
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
/**
* Type of values this deserializer handles: sometimes
* exact types, other time most specific supertype of
* types deserializer handles (which may be as generic
* as {@link Object} in some case)
*/
final protected Class<?> _valueClass;
protected StdDeserializer(Class<?> vc) {
_valueClass = vc;
}
protected StdDeserializer(JavaType valueType) {
_valueClass = (valueType == null) ? null : valueType.getRawClass();
}
/*
/**********************************************************
/* Extended API
/**********************************************************
*/
public Class<?> getValueClass() { return _valueClass; }
/**
* Exact structured type deserializer handles, if known.
*<p>
* Default implementation just returns null.
*/
public JavaType getValueType() { return null; }
/**
* Method that can be called to determine if given deserializer is the default
* deserializer Jackson uses; as opposed to a custom deserializer installed by
* a module or calling application. Determination is done using
* {@link JacksonStdImpl} annotation on deserializer class.
*/
protected boolean isDefaultDeserializer(JsonDeserializer<?> deserializer) {
return (deserializer != null && deserializer.getClass().getAnnotation(JacksonStdImpl.class) != null);
}
protected boolean isDefaultKeyDeserializer(KeyDeserializer keyDeser) {
return (keyDeser != null && keyDeser.getClass().getAnnotation(JacksonStdImpl.class) != null);
}
/*
/**********************************************************
/* Partial JsonDeserializer implementation
/**********************************************************
*/
/**
* Base implementation that does not assume specific type
* inclusion mechanism. Sub-classes are expected to override
* this method if they are to handle type information.
*/
@Override
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException
{
return typeDeserializer.deserializeTypedFromAny(jp, ctxt);
}
/*
/**********************************************************
/* Helper methods for sub-classes, parsing: while mostly
/* useful for numeric types, can be also useful for dealing
/* with things serialized as numbers (such as Dates).
/**********************************************************
*/
protected final boolean _parseBooleanPrimitive(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_TRUE) {
return true;
}
if (t == JsonToken.VALUE_FALSE) {
return false;
}
if (t == JsonToken.VALUE_NULL) {
return false;
}
// [JACKSON-78]: should accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
// 11-Jan-2012, tatus: May be outside of int...
if (jp.getNumberType() == NumberType.INT) {
return (jp.getIntValue() != 0);
}
return _parseBooleanFromNumber(jp, ctxt);
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
String text = jp.getText().trim();
if ("true".equals(text)) {
return true;
}
if ("false".equals(text) || text.length() == 0) {
return Boolean.FALSE;
}
throw ctxt.weirdStringException(text, _valueClass, "only \"true\" or \"false\" recognized");
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass, t);
}
protected final Boolean _parseBoolean(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_TRUE) {
return Boolean.TRUE;
}
if (t == JsonToken.VALUE_FALSE) {
return Boolean.FALSE;
}
// [JACKSON-78]: should accept ints too, (0 == false, otherwise true)
if (t == JsonToken.VALUE_NUMBER_INT) {
// 11-Jan-2012, tatus: May be outside of int...
if (jp.getNumberType() == NumberType.INT) {
return (jp.getIntValue() == 0) ? Boolean.FALSE : Boolean.TRUE;
}
return Boolean.valueOf(_parseBooleanFromNumber(jp, ctxt));
}
if (t == JsonToken.VALUE_NULL) {
return (Boolean) getNullValue();
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
String text = jp.getText().trim();
if ("true".equals(text)) {
return Boolean.TRUE;
}
if ("false".equals(text)) {
return Boolean.FALSE;
}
if (text.length() == 0) {
return (Boolean) getEmptyValue();
}
throw ctxt.weirdStringException(text, _valueClass, "only \"true\" or \"false\" recognized");
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass, t);
}
protected final boolean _parseBooleanFromNumber(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (jp.getNumberType() == NumberType.LONG) {
return (jp.getLongValue() == 0L) ? Boolean.FALSE : Boolean.TRUE;
}
// no really good logic; let's actually resort to textual comparison
String str = jp.getText();
if ("0.0".equals(str) || "0".equals(str)) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
protected Byte _parseByte(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
return jp.getByteValue();
}
if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
String text = jp.getText().trim();
int value;
try {
int len = text.length();
if (len == 0) {
return (Byte) getEmptyValue();
}
value = NumberInput.parseInt(text);
} catch (IllegalArgumentException iae) {
throw ctxt.weirdStringException(text, _valueClass, "not a valid Byte value");
}
// So far so good: but does it fit?
// as per [JACKSON-804], allow range up to 255, inclusive
if (value < Byte.MIN_VALUE || value > 255) {
throw ctxt.weirdStringException(text, _valueClass, "overflow, value can not be represented as 8-bit value");
}
return Byte.valueOf((byte) value);
}
if (t == JsonToken.VALUE_NULL) {
return (Byte) getNullValue();
}
throw ctxt.mappingException(_valueClass, t);
}
protected Short _parseShort(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
return jp.getShortValue();
}
if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
String text = jp.getText().trim();
int value;
try {
int len = text.length();
if (len == 0) {
return (Short) getEmptyValue();
}
value = NumberInput.parseInt(text);
} catch (IllegalArgumentException iae) {
throw ctxt.weirdStringException(text, _valueClass, "not a valid Short value");
}
// So far so good: but does it fit?
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
throw ctxt.weirdStringException(text, _valueClass, "overflow, value can not be represented as 16-bit value");
}
return Short.valueOf((short) value);
}
if (t == JsonToken.VALUE_NULL) {
return (Short) getNullValue();
}
throw ctxt.mappingException(_valueClass, t);
}
protected final short _parseShortPrimitive(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
int value = _parseIntPrimitive(jp, ctxt);
// So far so good: but does it fit?
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
throw ctxt.weirdStringException(String.valueOf(value),
_valueClass, "overflow, value can not be represented as 16-bit value");
}
return (short) value;
}
protected final int _parseIntPrimitive(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
// Int works as is, coercing fine as well
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
return jp.getIntValue();
}
if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
/* 31-Dec-2009, tatus: Should improve handling of overflow
* values... but this'll have to do for now
*/
String text = jp.getText().trim();
try {
int len = text.length();
if (len > 9) {
long l = Long.parseLong(text);
if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
throw ctxt.weirdStringException(text, _valueClass,
"Overflow: numeric value ("+text+") out of range of int ("+Integer.MIN_VALUE+" - "+Integer.MAX_VALUE+")");
}
return (int) l;
}
if (len == 0) {
return 0;
}
return NumberInput.parseInt(text);
} catch (IllegalArgumentException iae) {
throw ctxt.weirdStringException(text, _valueClass, "not a valid int value");
}
}
if (t == JsonToken.VALUE_NULL) {
return 0;
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass, t);
}
protected final Integer _parseInteger(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
return Integer.valueOf(jp.getIntValue());
}
if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
String text = jp.getText().trim();
try {
int len = text.length();
if (len > 9) {
long l = Long.parseLong(text);
if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
throw ctxt.weirdStringException(text, _valueClass,
"Overflow: numeric value ("+text+") out of range of Integer ("+Integer.MIN_VALUE+" - "+Integer.MAX_VALUE+")");
}
return Integer.valueOf((int) l);
}
if (len == 0) {
return (Integer) getEmptyValue();
}
return Integer.valueOf(NumberInput.parseInt(text));
} catch (IllegalArgumentException iae) {
throw ctxt.weirdStringException(text, _valueClass, "not a valid Integer value");
}
}
if (t == JsonToken.VALUE_NULL) {
return (Integer) getNullValue();
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass, t);
}
protected final Long _parseLong(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
// it should be ok to coerce (although may fail, too)
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) {
return jp.getLongValue();
}
// let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
// !!! 05-Jan-2009, tatu: Should we try to limit value space, JDK is too lenient?
String text = jp.getText().trim();
if (text.length() == 0) {
return (Long) getEmptyValue();
}
try {
return Long.valueOf(NumberInput.parseLong(text));
} catch (IllegalArgumentException iae) { }
throw ctxt.weirdStringException(text, _valueClass, "not a valid Long value");
}
if (t == JsonToken.VALUE_NULL) {
return (Long) getNullValue();
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass, t);
}
protected final long _parseLongPrimitive(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) {
return jp.getLongValue();
}
if (t == JsonToken.VALUE_STRING) {
String text = jp.getText().trim();
if (text.length() == 0) {
return 0L;
}
try {
return NumberInput.parseLong(text);
} catch (IllegalArgumentException iae) { }
throw ctxt.weirdStringException(text, _valueClass, "not a valid long value");
}
if (t == JsonToken.VALUE_NULL) {
return 0L;
}
throw ctxt.mappingException(_valueClass, t);
}
protected final Float _parseFloat(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// We accept couple of different types; obvious ones first:
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
return jp.getFloatValue();
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
String text = jp.getText().trim();
if (text.length() == 0) {
return (Float) getEmptyValue();
}
switch (text.charAt(0)) {
case 'I':
if ("Infinity".equals(text) || "INF".equals(text)) {
return Float.POSITIVE_INFINITY;
}
break;
case 'N':
if ("NaN".equals(text)) {
return Float.NaN;
}
break;
case '-':
if ("-Infinity".equals(text) || "-INF".equals(text)) {
return Float.NEGATIVE_INFINITY;
}
break;
}
try {
return Float.parseFloat(text);
} catch (IllegalArgumentException iae) { }
throw ctxt.weirdStringException(text, _valueClass, "not a valid Float value");
}
if (t == JsonToken.VALUE_NULL) {
return (Float) getNullValue();
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass, t);
}
protected final float _parseFloatPrimitive(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
return jp.getFloatValue();
}
if (t == JsonToken.VALUE_STRING) {
String text = jp.getText().trim();
if (text.length() == 0) {
return 0.0f;
}
switch (text.charAt(0)) {
case 'I':
if ("Infinity".equals(text) || "INF".equals(text)) {
return Float.POSITIVE_INFINITY;
}
break;
case 'N':
if ("NaN".equals(text)) {
return Float.NaN;
}
break;
case '-':
if ("-Infinity".equals(text) || "-INF".equals(text)) {
return Float.NEGATIVE_INFINITY;
}
break;
}
try {
return Float.parseFloat(text);
} catch (IllegalArgumentException iae) { }
throw ctxt.weirdStringException(text, _valueClass, "not a valid float value");
}
if (t == JsonToken.VALUE_NULL) {
return 0.0f;
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass, t);
}
protected final Double _parseDouble(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
return jp.getDoubleValue();
}
if (t == JsonToken.VALUE_STRING) {
String text = jp.getText().trim();
if (text.length() == 0) {
return (Double) getEmptyValue();
}
switch (text.charAt(0)) {
case 'I':
if ("Infinity".equals(text) || "INF".equals(text)) {
return Double.POSITIVE_INFINITY;
}
break;
case 'N':
if ("NaN".equals(text)) {
return Double.NaN;
}
break;
case '-':
if ("-Infinity".equals(text) || "-INF".equals(text)) {
return Double.NEGATIVE_INFINITY;
}
break;
}
try {
return parseDouble(text);
} catch (IllegalArgumentException iae) { }
throw ctxt.weirdStringException(text, _valueClass, "not a valid Double value");
}
if (t == JsonToken.VALUE_NULL) {
return (Double) getNullValue();
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass, t);
}
protected final double _parseDoublePrimitive(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// We accept couple of different types; obvious ones first:
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
return jp.getDoubleValue();
}
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
String text = jp.getText().trim();
if (text.length() == 0) {
return 0.0;
}
switch (text.charAt(0)) {
case 'I':
if ("Infinity".equals(text) || "INF".equals(text)) {
return Double.POSITIVE_INFINITY;
}
break;
case 'N':
if ("NaN".equals(text)) {
return Double.NaN;
}
break;
case '-':
if ("-Infinity".equals(text) || "-INF".equals(text)) {
return Double.NEGATIVE_INFINITY;
}
break;
}
try {
return parseDouble(text);
} catch (IllegalArgumentException iae) { }
throw ctxt.weirdStringException(text, _valueClass, "not a valid double value");
}
if (t == JsonToken.VALUE_NULL) {
return 0.0;
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass, t);
}
protected java.util.Date _parseDate(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT) {
return new java.util.Date(jp.getLongValue());
}
if (t == JsonToken.VALUE_NULL) {
return (java.util.Date) getNullValue();
}
if (t == JsonToken.VALUE_STRING) {
String value = null;
try {
// As per [JACKSON-203], take empty Strings to mean
value = jp.getText().trim();
if (value.length() == 0) {
return (Date) getEmptyValue();
}
return ctxt.parseDate(value);
} catch (IllegalArgumentException iae) {
throw ctxt.weirdStringException(value, _valueClass,
"not a valid representation (error: "+iae.getMessage()+")");
}
}
throw ctxt.mappingException(_valueClass, t);
}
/**
* Helper method for encapsulating calls to low-level double value parsing; single place
* just because we need a work-around that must be applied to all calls.
*/
protected final static double parseDouble(String numStr) throws NumberFormatException
{
// [JACKSON-486]: avoid some nasty float representations... but should it be MIN_NORMAL or MIN_VALUE?
// for now, MIN_VALUE, since MIN_NORMAL is JDK 1.6
if (NumberInput.NASTY_SMALL_DOUBLE.equals(numStr)) {
return Double.MIN_VALUE;
}
return Double.parseDouble(numStr);
}
/**
* Helper method used for accessing String value, if possible, doing
* necessary conversion or throwing exception as necessary.
*
* @since 2.1
*/
protected final String _parseString(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
String value = jp.getValueAsString();
if (value != null) {
return value;
}
throw ctxt.mappingException(String.class, jp.getCurrentToken());
}
/*
/****************************************************
/* Helper methods for sub-classes, resolving dependencies
/****************************************************
*/
/**
* Helper method used to locate deserializers for properties the
* type this deserializer handles contains (usually for properties of
* bean types)
*
* @param type Type of property to deserialize
* @param property Actual property object (field, method, constuctor parameter) used
* for passing deserialized values; provided so deserializer can be contextualized if necessary (since 1.7)
*/
protected JsonDeserializer<Object> findDeserializer(DeserializationContext ctxt,
JavaType type, BeanProperty property)
throws JsonMappingException
{
return ctxt.findContextualValueDeserializer(type, property);
}
/*
/**********************************************************
/* Helper methods for sub-classes, problem reporting
/**********************************************************
*/
/**
* Method called to deal with a property that did not map to a known
* Bean property. Method can deal with the problem as it sees fit (ignore,
* throw exception); but if it does return, it has to skip the matching
* Json content parser has.
*<p>
* NOTE: method signature was changed in version 1.5; explicit JsonParser
* <b>must</b> be passed since it may be something other than what
* context has. Prior versions did not include the first parameter.
*
* @param jp Parser that points to value of the unknown property
* @param ctxt Context for deserialization; allows access to the parser,
* error reporting functionality
* @param instanceOrClass Instance that is being populated by this
* deserializer, or if not known, Class that would be instantiated.
* If null, will assume type is what {@link #getValueClass} returns.
* @param propName Name of the property that can not be mapped
*/
protected void handleUnknownProperty(JsonParser jp, DeserializationContext ctxt,
Object instanceOrClass, String propName)
throws IOException, JsonProcessingException
{
if (instanceOrClass == null) {
instanceOrClass = getValueClass();
}
// Maybe we have configured handler(s) to take care of it?
if (ctxt.handleUnknownProperty(jp, this, instanceOrClass, propName)) {
return;
}
// Nope, not handled. Potentially that's a problem...
ctxt.reportUnknownProperty(instanceOrClass, propName, this);
/* But if we do get this far, need to skip whatever value we
* are pointing to now.
*/
jp.skipChildren();
}
}