Implemented [JACKSON-349], allow empty String as null/0 number
diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/ArrayDeserializers.java b/src/mapper/java/org/codehaus/jackson/map/deser/ArrayDeserializers.java
index 21f1723..b6b629f 100644
--- a/src/mapper/java/org/codehaus/jackson/map/deser/ArrayDeserializers.java
+++ b/src/mapper/java/org/codehaus/jackson/map/deser/ArrayDeserializers.java
@@ -293,7 +293,7 @@
int ix = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
- short value = _parseShort(jp, ctxt);
+ short value = _parseShortPrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
@@ -322,7 +322,7 @@
while (jp.nextToken() != JsonToken.END_ARRAY) {
// whether we should allow truncating conversions?
- int value = _parseInt(jp, ctxt);
+ int value = _parseIntPrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
@@ -350,7 +350,7 @@
int ix = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
- long value = _parseLong(jp, ctxt);
+ long value = _parseLongPrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
@@ -379,7 +379,7 @@
while (jp.nextToken() != JsonToken.END_ARRAY) {
// whether we should allow truncating conversions?
- float value = _parseFloat(jp, ctxt);
+ float value = _parseFloatPrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
@@ -407,7 +407,7 @@
int ix = 0;
while (jp.nextToken() != JsonToken.END_ARRAY) {
- double value = _parseDouble(jp, ctxt);
+ double value = _parseDoublePrimitive(jp, ctxt);
if (ix >= chunk.length) {
chunk = builder.appendCompletedChunk(chunk, ix);
ix = 0;
diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java b/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java
index 101f455..5a71453 100644
--- a/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java
+++ b/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java
@@ -108,10 +108,17 @@
throw ctxt.mappingException(_valueClass);
}
- protected final short _parseShort(JsonParser jp, DeserializationContext ctxt)
+ protected final Short _parseShort(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
- int value = _parseInt(jp, ctxt);
+ JsonToken t = jp.getCurrentToken();
+ if (t == JsonToken.VALUE_NULL) {
+ return (short) 0;
+ }
+ if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
+ return jp.getShortValue();
+ }
+ 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");
@@ -119,7 +126,18 @@
return (short) value;
}
- protected final int _parseInt(JsonParser jp, DeserializationContext ctxt)
+ 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();
@@ -134,16 +152,19 @@
*/
String text = jp.getText().trim();
try {
- if (text.length() > 9) {
+ 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;
- } else {
- return Integer.parseInt(text);
}
+ if (len == 0) {
+ return 0;
+ }
+ return Integer.parseInt(text);
} catch (IllegalArgumentException iae) {
throw ctxt.weirdStringException(_valueClass, "not a valid int value");
}
@@ -155,11 +176,45 @@
throw ctxt.mappingException(_valueClass);
}
- protected final long _parseLong(JsonParser jp, DeserializationContext ctxt)
+ 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 null;
+ }
+ return Integer.parseInt(text);
+ } catch (IllegalArgumentException iae) {
+ throw ctxt.weirdStringException(_valueClass, "not a valid Integer value");
+ }
+ }
+ if (t == JsonToken.VALUE_NULL) {
+ return null;
+ }
+ // Otherwise, no can do:
+ throw ctxt.mappingException(_valueClass);
+ }
+ 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();
@@ -168,16 +223,45 @@
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 null;
+ }
try {
return Long.parseLong(text);
} catch (IllegalArgumentException iae) { }
- throw ctxt.weirdStringException(_valueClass, "not a valid long value");
+ throw ctxt.weirdStringException(_valueClass, "not a valid Long value");
+ }
+ if (t == JsonToken.VALUE_NULL) {
+ return null;
}
// Otherwise, no can do:
throw ctxt.mappingException(_valueClass);
}
- protected final float _parseFloat(JsonParser jp, DeserializationContext ctxt)
+ 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 Long.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);
+ }
+
+ protected final Float _parseFloat(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// We accept couple of different types; obvious ones first:
@@ -188,31 +272,73 @@
}
// And finally, 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() > 1) {
- 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;
+ if (text.length() == 0) {
+ return null;
+ }
+ 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 double value");
+ throw ctxt.weirdStringException(_valueClass, "not a valid Float value");
+ }
+ if (t == JsonToken.VALUE_NULL) {
+ return null;
+ }
+ // Otherwise, no can do:
+ throw ctxt.mappingException(_valueClass);
+ }
+
+ 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;
@@ -221,7 +347,49 @@
throw ctxt.mappingException(_valueClass);
}
- protected final double _parseDouble(JsonParser jp, DeserializationContext ctxt)
+ 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 null;
+ }
+ 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 Double.parseDouble(text);
+ } catch (IllegalArgumentException iae) { }
+ throw ctxt.weirdStringException(_valueClass, "not a valid Double value");
+ }
+ if (t == JsonToken.VALUE_NULL) {
+ return null;
+ }
+ // Otherwise, no can do:
+ throw ctxt.mappingException(_valueClass);
+ }
+
+ protected final double _parseDoublePrimitive(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
// We accept couple of different types; obvious ones first:
@@ -233,24 +401,25 @@
// And finally, let's allow Strings to be converted too
if (t == JsonToken.VALUE_STRING) {
String text = jp.getText().trim();
- if (text.length() > 1) {
- 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;
+ 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 Double.parseDouble(text);
@@ -264,6 +433,7 @@
throw ctxt.mappingException(_valueClass);
}
+
protected java.util.Date _parseDate(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
@@ -514,7 +684,7 @@
public Byte deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
- int value = _parseInt(jp, ctxt);
+ int value = _parseIntPrimitive(jp, ctxt);
// 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");
@@ -536,7 +706,7 @@
public Short deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
- return Short.valueOf(_parseShort(jp, ctxt));
+ return _parseShort(jp, ctxt);
}
}
@@ -585,7 +755,7 @@
public Integer deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
- return _parseInt(jp, ctxt);
+ return _parseInteger(jp, ctxt);
}
// 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double):
@@ -595,7 +765,7 @@
TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException
{
- return _parseInt(jp, ctxt);
+ return _parseInteger(jp, ctxt);
}
}
@@ -612,7 +782,7 @@
public Long deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
- return Long.valueOf(_parseLong(jp, ctxt));
+ return _parseLong(jp, ctxt);
}
}
@@ -632,7 +802,7 @@
/* 22-Jan-2009, tatu: Bounds/range checks would be tricky
* here, so let's not bother even trying...
*/
- return Float.valueOf((float) _parseDouble(jp, ctxt));
+ return _parseFloat(jp, ctxt);
}
}
@@ -831,13 +1001,15 @@
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
- if (t == JsonToken.VALUE_NUMBER_INT
- || t == JsonToken.VALUE_NUMBER_FLOAT) {
+ 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) {
@@ -882,6 +1054,9 @@
throw ctxt.mappingException(_valueClass);
}
text = jp.getText().trim();
+ if (text.length() == 0) {
+ return null;
+ }
try {
return new BigInteger(text);
} catch (IllegalArgumentException iae) {
diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/StdKeyDeserializer.java b/src/mapper/java/org/codehaus/jackson/map/deser/StdKeyDeserializer.java
index 188ebe7..875e498 100644
--- a/src/mapper/java/org/codehaus/jackson/map/deser/StdKeyDeserializer.java
+++ b/src/mapper/java/org/codehaus/jackson/map/deser/StdKeyDeserializer.java
@@ -13,7 +13,7 @@
public abstract class StdKeyDeserializer
extends KeyDeserializer
{
- final Class<?> _keyClass;
+ final protected Class<?> _keyClass;
protected StdKeyDeserializer(Class<?> cls) { _keyClass = cls; }
@@ -40,9 +40,9 @@
protected abstract Object _parse(String key, DeserializationContext ctxt) throws Exception;
/*
- ////////////////////////////////////////////////////////////////////////
- // Helper methods for sub-classes
- ////////////////////////////////////////////////////////////////////////
+ /**********************************************************
+ /* Helper methods for sub-classes
+ /**********************************************************
*/
protected int _parseInt(String key) throws IllegalArgumentException
@@ -61,9 +61,9 @@
}
/*
- ////////////////////////////////////////////////////////////////////////
- // Key deserializer implementations; wrappers
- ////////////////////////////////////////////////////////////////////////
+ /**********************************************************
+ /* Key deserializer implementations; wrappers
+ /**********************************************************
*/
final static class BoolKD extends StdKeyDeserializer
@@ -147,7 +147,7 @@
LongKD() { super(Long.class); }
@Override
- public Long _parse(String key, DeserializationContext ctxt) throws JsonMappingException
+ public Long _parse(String key, DeserializationContext ctxt) throws JsonMappingException
{
return _parseLong(key);
}
@@ -158,7 +158,7 @@
DoubleKD() { super(Double.class); }
@Override
- public Double _parse(String key, DeserializationContext ctxt) throws JsonMappingException
+ public Double _parse(String key, DeserializationContext ctxt) throws JsonMappingException
{
return _parseDouble(key);
}
@@ -169,7 +169,7 @@
FloatKD() { super(Float.class); }
@Override
- public Float _parse(String key, DeserializationContext ctxt) throws JsonMappingException
+ public Float _parse(String key, DeserializationContext ctxt) throws JsonMappingException
{
/* 22-Jan-2009, tatu: Bounds/range checks would be tricky
* here, so let's not bother even trying...
@@ -179,9 +179,9 @@
}
/*
- ////////////////////////////////////////////////////////////////////////
- // Key deserializer implementations; other
- ////////////////////////////////////////////////////////////////////////
+ /**********************************************************
+ /* Key deserializer implementations; other
+ /**********************************************************
*/
final static class EnumKD extends StdKeyDeserializer
diff --git a/src/test/org/codehaus/jackson/map/deser/TestNumbers.java b/src/test/org/codehaus/jackson/map/deser/TestNumbers.java
index a173254..c66d401 100644
--- a/src/test/org/codehaus/jackson/map/deser/TestNumbers.java
+++ b/src/test/org/codehaus/jackson/map/deser/TestNumbers.java
@@ -1,5 +1,8 @@
package org.codehaus.jackson.map.deser;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
import org.codehaus.jackson.map.*;
/**
@@ -24,4 +27,16 @@
result = m.readValue(" \""+Double.NEGATIVE_INFINITY+"\"", Double.class);
assertEquals(Double.valueOf(Double.NEGATIVE_INFINITY), result);
}
+
+ // [JACKSON-349]
+ public void testEmptyAsNumber() throws Exception
+ {
+ ObjectMapper m = new ObjectMapper();
+ assertNull(m.readValue(quote(""), Integer.class));
+ assertNull(m.readValue(quote(""), Long.class));
+ assertNull(m.readValue(quote(""), Float.class));
+ assertNull(m.readValue(quote(""), Double.class));
+ assertNull(m.readValue(quote(""), BigInteger.class));
+ assertNull(m.readValue(quote(""), BigDecimal.class));
+ }
}