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(); } }