All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.jackson.map.deser.std.StdDeserializer Maven / Gradle / Ivy

Go to download

Data Mapper package is a high-performance data binding package built on Jackson JSON processor

There is a newer version: 1.9.13
Show newest version
package org.codehaus.jackson.map.deser.std;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.io.NumberInput;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.annotate.JacksonStdImpl;
import org.codehaus.jackson.type.JavaType;

/**
 * Base class for common deserializers. Contains shared
 * base functionality for dealing with primitive values, such
 * as (re)parsing from String.
 * 
 * @since 1.9 (moved from higher-level package)
 */
public abstract class StdDeserializer
    extends JsonDeserializer
{
    /**
     * 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.
     *

* 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. * * @since 1.7 */ protected boolean isDefaultSerializer(JsonDeserializer deserializer) { return (deserializer != null && deserializer.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 /********************************************************** */ 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) { return (jp.getIntValue() != 0); } // 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(_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) { return (jp.getIntValue() == 0) ? Boolean.FALSE : Boolean.TRUE; } 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(_valueClass, "only \"true\" or \"false\" recognized"); } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } 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(_valueClass, "not a valid Byte value"); } // So far so good: but does it fit? if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { throw ctxt.weirdStringException(_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(_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(_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(_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(_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(_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(_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(_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(_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(_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(_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(_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(_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(_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) { try { /* As per [JACKSON-203], take empty Strings to mean * null */ String str = jp.getText().trim(); if (str.length() == 0) { return (Date) getEmptyValue(); } return ctxt.parseDate(str); } catch (IllegalArgumentException iae) { throw ctxt.weirdStringException(_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. *

* Note: copied from org.codehaus.jackson.io.NumberUtil (to avoid dependency to * version 1.8; except for String constants, but that gets compiled in bytecode here) */ 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? if (NumberInput.NASTY_SMALL_DOUBLE.equals(numStr)) { return Double.MIN_NORMAL; } return Double.parseDouble(numStr); } /* /**************************************************** /* 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 config Active deserialization configuration * @param provider Deserializer provider to use for actually finding deserializer(s) * @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 findDeserializer(DeserializationConfig config, DeserializerProvider provider, JavaType type, BeanProperty property) throws JsonMappingException { JsonDeserializer deser = provider.findValueDeserializer(config, type, property); return deser; } /* /********************************************************** /* 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. *

* NOTE: method signature was changed in version 1.5; explicit JsonParser * must 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... reportUnknownProperty(ctxt, instanceOrClass, propName); /* If we get this far, need to skip now; we point to first token of * value (START_xxx for structured, or the value token for others) */ jp.skipChildren(); } protected void reportUnknownProperty(DeserializationContext ctxt, Object instanceOrClass, String fieldName) throws IOException, JsonProcessingException { // throw exception if that's what we are expected to do if (ctxt.isEnabled(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES)) { throw ctxt.unknownFieldException(instanceOrClass, fieldName); } // ... or if not, just ignore } /* /********************************************************** /* Then one intermediate base class for things that have /* both primitive and wrapper types /********************************************************** */ protected abstract static class PrimitiveOrWrapperDeserializer extends StdScalarDeserializer { final T _nullValue; protected PrimitiveOrWrapperDeserializer(Class vc, T nvl) { super(vc); _nullValue = nvl; } @Override public final T getNullValue() { return _nullValue; } } /* /********************************************************** /* Then primitive/wrapper types /********************************************************** */ @JacksonStdImpl public final static class BooleanDeserializer extends PrimitiveOrWrapperDeserializer { public BooleanDeserializer(Class cls, Boolean nvl) { super(cls, nvl); } @Override public Boolean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { return _parseBoolean(jp, ctxt); } // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): // (is it an error to even call this version?) @Override public Boolean deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException, JsonProcessingException { return _parseBoolean(jp, ctxt); } } @JacksonStdImpl public final static class ByteDeserializer extends PrimitiveOrWrapperDeserializer { public ByteDeserializer(Class cls, Byte nvl) { super(cls, nvl); } @Override public Byte deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { return _parseByte(jp, ctxt); } } @JacksonStdImpl public final static class ShortDeserializer extends PrimitiveOrWrapperDeserializer { public ShortDeserializer(Class cls, Short nvl) { super(cls, nvl); } @Override public Short deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { return _parseShort(jp, ctxt); } } @JacksonStdImpl public final static class CharacterDeserializer extends PrimitiveOrWrapperDeserializer { public CharacterDeserializer(Class cls, Character nvl) { super(cls, nvl); } @Override public Character deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); int value; if (t == JsonToken.VALUE_NUMBER_INT) { // ok iff ascii value value = jp.getIntValue(); if (value >= 0 && value <= 0xFFFF) { return Character.valueOf((char) value); } } else if (t == JsonToken.VALUE_STRING) { // this is the usual type // But does it have to be exactly one char? String text = jp.getText(); if (text.length() == 1) { return Character.valueOf(text.charAt(0)); } // actually, empty should become null? if (text.length() == 0) { return (Character) getEmptyValue(); } } throw ctxt.mappingException(_valueClass, t); } } @JacksonStdImpl public final static class IntegerDeserializer extends PrimitiveOrWrapperDeserializer { public IntegerDeserializer(Class cls, Integer nvl) { super(cls, nvl); } @Override public Integer deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { return _parseInteger(jp, ctxt); } // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): // (is it an error to even call this version?) @Override public Integer deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException, JsonProcessingException { return _parseInteger(jp, ctxt); } } @JacksonStdImpl public final static class LongDeserializer extends PrimitiveOrWrapperDeserializer { public LongDeserializer(Class cls, Long nvl) { super(cls, nvl); } @Override public Long deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { return _parseLong(jp, ctxt); } } @JacksonStdImpl public final static class FloatDeserializer extends PrimitiveOrWrapperDeserializer { public FloatDeserializer(Class cls, Float nvl) { super(cls, nvl); } @Override public Float deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { /* 22-Jan-2009, tatu: Bounds/range checks would be tricky * here, so let's not bother even trying... */ return _parseFloat(jp, ctxt); } } @JacksonStdImpl public final static class DoubleDeserializer extends PrimitiveOrWrapperDeserializer { public DoubleDeserializer(Class cls, Double nvl) { super(cls, nvl); } @Override public Double deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { return _parseDouble(jp, ctxt); } // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double): // (is it an error to even call this version?) @Override public Double deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException, JsonProcessingException { return _parseDouble(jp, ctxt); } } /** * For type Number.class, we can just rely on type * mappings that plain {@link JsonParser#getNumberValue} returns. *

* Since 1.5, there is one additional complication: some numeric * types (specifically, int/Integer and double/Double) are "non-typed"; * meaning that they will NEVER be output with type information. * But other numeric types may need such type information. * This is why {@link #deserializeWithType} must be overridden. */ @JacksonStdImpl public final static class NumberDeserializer extends StdScalarDeserializer { public NumberDeserializer() { super(Number.class); } @Override public Number deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); if (t == JsonToken.VALUE_NUMBER_INT) { if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_INTEGER_FOR_INTS)) { return jp.getBigIntegerValue(); } return jp.getNumberValue(); } else if (t == JsonToken.VALUE_NUMBER_FLOAT) { /* [JACKSON-72]: need to allow overriding the behavior * regarding which type to use */ if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_DECIMAL_FOR_FLOATS)) { return jp.getDecimalValue(); } return Double.valueOf(jp.getDoubleValue()); } /* Textual values are more difficult... not parsing itself, but figuring * out 'minimal' type to use */ if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse String text = jp.getText().trim(); try { if (text.indexOf('.') >= 0) { // floating point // as per [JACKSON-72]: if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_DECIMAL_FOR_FLOATS)) { return new BigDecimal(text); } return new Double(text); } // as per [JACKSON-100]: if (ctxt.isEnabled(DeserializationConfig.Feature.USE_BIG_INTEGER_FOR_INTS)) { return new BigInteger(text); } long value = Long.parseLong(text); if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) { return Integer.valueOf((int) value); } return Long.valueOf(value); } catch (IllegalArgumentException iae) { throw ctxt.weirdStringException(_valueClass, "not a valid number"); } } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } /** * As mentioned in class Javadoc, there is additional complexity in * handling potentially mixed type information here. Because of this, * we must actually check for "raw" integers and doubles first, before * calling type deserializer. */ @Override public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException, JsonProcessingException { switch (jp.getCurrentToken()) { case VALUE_NUMBER_INT: case VALUE_NUMBER_FLOAT: case VALUE_STRING: // can not point to type information: hence must be non-typed (int/double) return deserialize(jp, ctxt); } return typeDeserializer.deserializeTypedFromScalar(jp, ctxt); } } /* /********************************************************** /* And then bit more complicated (but non-structured) number /* types /********************************************************** */ @JacksonStdImpl public static class BigDecimalDeserializer extends StdScalarDeserializer { public BigDecimalDeserializer() { super(BigDecimal.class); } @Override public BigDecimal deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { return jp.getDecimalValue(); } // String is ok too, can easily convert if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse String text = jp.getText().trim(); if (text.length() == 0) { return null; } try { return new BigDecimal(text); } catch (IllegalArgumentException iae) { throw ctxt.weirdStringException(_valueClass, "not a valid representation"); } } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } } /** * This is bit trickier to implement efficiently, while avoiding * overflow problems. */ @JacksonStdImpl public static class BigIntegerDeserializer extends StdScalarDeserializer { public BigIntegerDeserializer() { super(BigInteger.class); } @Override public BigInteger deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); String text; if (t == JsonToken.VALUE_NUMBER_INT) { switch (jp.getNumberType()) { case INT: case LONG: return BigInteger.valueOf(jp.getLongValue()); } } else if (t == JsonToken.VALUE_NUMBER_FLOAT) { /* Whether to fail if there's non-integer part? * Could do by calling BigDecimal.toBigIntegerExact() */ return jp.getDecimalValue().toBigInteger(); } else if (t != JsonToken.VALUE_STRING) { // let's do implicit re-parse // String is ok too, can easily convert; otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } text = jp.getText().trim(); if (text.length() == 0) { return null; } try { return new BigInteger(text); } catch (IllegalArgumentException iae) { throw ctxt.weirdStringException(_valueClass, "not a valid representation"); } } } /* /**************************************************** /* Then trickier things: Date/Calendar types /**************************************************** */ /** * Compared to plain old {@link java.util.Date}, SQL version is easier * to deal with: mostly because it is more limited. */ public static class SqlDateDeserializer extends StdScalarDeserializer { public SqlDateDeserializer() { super(java.sql.Date.class); } @Override public java.sql.Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { Date d = _parseDate(jp, ctxt); return (d == null) ? null : new java.sql.Date(d.getTime()); } } /* /**************************************************** /* And other oddities /**************************************************** */ public static class StackTraceElementDeserializer extends StdScalarDeserializer { public StackTraceElementDeserializer() { super(StackTraceElement.class); } @Override public StackTraceElement deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonToken t = jp.getCurrentToken(); // Must get an Object if (t == JsonToken.START_OBJECT) { String className = "", methodName = "", fileName = ""; int lineNumber = -1; while ((t = jp.nextValue()) != JsonToken.END_OBJECT) { String propName = jp.getCurrentName(); if ("className".equals(propName)) { className = jp.getText(); } else if ("fileName".equals(propName)) { fileName = jp.getText(); } else if ("lineNumber".equals(propName)) { if (t.isNumeric()) { lineNumber = jp.getIntValue(); } else { throw JsonMappingException.from(jp, "Non-numeric token ("+t+") for property 'lineNumber'"); } } else if ("methodName".equals(propName)) { methodName = jp.getText(); } else if ("nativeMethod".equals(propName)) { // no setter, not passed via constructor: ignore } else { handleUnknownProperty(jp, ctxt, _valueClass, propName); } } return new StackTraceElement(className, methodName, fileName, lineNumber); } throw ctxt.mappingException(_valueClass, t); } } }