From 0e37a39502439ecbaa1a5b5188387c01bf7f7fa1 Mon Sep 17 00:00:00 2001 From: Matthew Morrissette Date: Tue, 7 Jan 2014 20:04:02 -0800 Subject: [PATCH] Adding "UNWRAP_SINGLE_VALUE_ARRAYS" DeserializationFeature Fixes issue #381. Added new feature that determines whether it is acceptable to coerce a single value array (in JSON) to the corresponding value type. --- .../databind/DeserializationFeature.java | 9 + .../databind/deser/std/DateDeserializers.java | 37 ++- .../databind/deser/std/EnumDeserializer.java | 13 + .../deser/std/FromStringDeserializer.java | 11 + .../deser/std/NumberDeserializers.java | 52 +++- .../std/StackTraceElementDeserializer.java | 11 + .../databind/deser/std/StdDeserializer.java | 153 ++++++++++ .../deser/std/StringDeserializer.java | 11 + .../jackson/databind/deser/TestJdkTypes.java | 279 ++++++++++++++++++ 9 files changed, 561 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java index 4dc97e3f36..8ced4b67bf 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java @@ -203,6 +203,15 @@ public enum DeserializationFeature implements ConfigFeature * Feature is disabled by default. */ ACCEPT_SINGLE_VALUE_AS_ARRAY(false), + + /** + * Feature that determines whether it is acceptable to coerce single value array (in JSON) + * values to the corresponding value type. This is basically the opposite of the {@link #ACCEPT_SINGLE_VALUE_AS_ARRAY} + * feature. If more than one value is found in the array, a JsonMappingException is thrown. + *

+ * Feature is disabled by default + */ + UNWRAP_SINGLE_VALUE_ARRAYS(false), /** * Feature to allow "unwrapping" root-level JSON value, to match setting of diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java index a1e4b46fd5..b5d182f14d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; @@ -143,19 +144,33 @@ public JsonDeserializer createContextual(DeserializationContext ctxt, BeanPro protected java.util.Date _parseDate(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { - if (_customFormat != null && jp.getCurrentToken() == JsonToken.VALUE_STRING) { - String str = jp.getText().trim(); - if (str.length() == 0) { - return (Date) getEmptyValue(); - } - synchronized (_customFormat) { - try { - return _customFormat.parse(str); - } catch (ParseException e) { - throw new IllegalArgumentException("Failed to parse Date value '"+str - +"' (format: \""+_formatString+"\"): "+e.getMessage()); + if (_customFormat != null) { + JsonToken t = jp.getCurrentToken(); + if (t == JsonToken.VALUE_STRING) { + String str = jp.getText().trim(); + if (str.length() == 0) { + return (Date) getEmptyValue(); + } + synchronized (_customFormat) { + try { + return _customFormat.parse(str); + } catch (ParseException e) { + throw new IllegalArgumentException("Failed to parse Date value '"+str + +"' (format: \""+_formatString+"\"): "+e.getMessage()); + } } } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Date parsed = _parseDate(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'java.util.Date' value but there was more than a single value in the array"); + } + return parsed; + } } return super._parseDate(jp, ctxt); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java index dab3d00b05..1fddc4d11a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java @@ -111,6 +111,19 @@ public Enum deserialize(JsonParser jp, DeserializationContext ctxt) } return result; } + + // Issue#381 + if (curr == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Enum parsed = deserialize(jp, ctxt); + curr = jp.nextToken(); + if (curr != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single '" + _resolver.getEnumClass().getName() + "' value but there was more than a single value in the array"); + } + return parsed; + } + throw ctxt.mappingException(_resolver.getEnumClass()); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java index 2d4aa3f17a..dab1d39074 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.fasterxml.jackson.databind.util.ClassUtil; @@ -98,6 +99,16 @@ public static Std findDeserializer(Class rawType) @Override public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + // Issue#381 + if (jp.getCurrentToken() == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final T value = deserialize(jp, ctxt); + if (jp.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single '" + _valueClass.getName() + "' value but there was more than a single value in the array"); + } + return value; + } // 22-Sep-2012, tatu: For 2.1, use this new method, may force coercion: String text = jp.getValueAsString(); if (text != null) { // has String representation diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java index 27501e449c..606d847bc5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java @@ -238,10 +238,9 @@ 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(); + int value = jp.getIntValue(); if (value >= 0 && value <= 0xFFFF) { return Character.valueOf((char) value); } @@ -254,7 +253,21 @@ public Character deserialize(JsonParser jp, DeserializationContext ctxt) // actually, empty should become null? if (text.length() == 0) { return (Character) getEmptyValue(); + } + } else if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + //Issue#381 + jp.nextToken(); + final Character value = deserialize(jp, ctxt); + if (jp.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single '" + _valueClass.getName() + "' value but there was more than a single value in the array" + ); } + return value; + } else if (t == JsonToken.VALUE_NULL && !_valueClass.isPrimitive()) { + //Issue#unreported + // This handles the case where the value required is the Character wrapper class and the token is the null token + return getEmptyValue(); } throw ctxt.mappingException(_valueClass, t); } @@ -436,6 +449,17 @@ public Number deserialize(JsonParser jp, DeserializationContext ctxt) throw ctxt.weirdStringException(text, _valueClass, "not a valid number"); } } + + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Number value = deserialize(jp, ctxt); + if (jp.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single '" + _valueClass.getName() + "' value but there was more than a single value in the array" + ); + } + return value; + } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } @@ -502,10 +526,19 @@ public BigInteger deserialize(JsonParser jp, DeserializationContext ctxt) * Could do by calling BigDecimal.toBigIntegerExact() */ return jp.getDecimalValue().toBigInteger(); + } else if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final BigInteger value = deserialize(jp, ctxt); + if (jp.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'BigInteger' value but there was more than a single value in the array" + ); + } + return value; } 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; @@ -547,6 +580,17 @@ public BigDecimal deserialize(JsonParser jp, DeserializationContext ctxt) throw ctxt.weirdStringException(text, _valueClass, "not a valid representation"); } } + + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final BigDecimal value = deserialize(jp, ctxt); + if (jp.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'BigDecimal' value but there was more than a single value in the array" + ); + } + return value; + } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java index 2be93943d6..6dc16a3880 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonMappingException; public class StackTraceElementDeserializer @@ -45,7 +46,17 @@ public StackTraceElement deserialize(JsonParser jp, DeserializationContext ctxt) } } return new StackTraceElement(className, methodName, fileName, lineNumber); + } else if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final StackTraceElement value = deserialize(jp, ctxt); + if (jp.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'java.lang.StackTraceElement' value but there was more than a single value in the array" + ); + } + return value; } + throw ctxt.mappingException(_valueClass, t); } } \ No newline at end of file diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java index cf236b9273..4f1229a44b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java @@ -134,6 +134,17 @@ protected final boolean _parseBooleanPrimitive(JsonParser jp, DeserializationCon } throw ctxt.weirdStringException(text, _valueClass, "only \"true\" or \"false\" recognized"); } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final boolean parsed = _parseBooleanPrimitive(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'boolean' value but there was more than a single value in the array"); + } + return parsed; + } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } @@ -176,6 +187,17 @@ protected final Boolean _parseBoolean(JsonParser jp, DeserializationContext ctxt } throw ctxt.weirdStringException(text, _valueClass, "only \"true\" or \"false\" recognized"); } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Boolean parsed = _parseBoolean(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'Boolean' value but there was more than a single value in the array"); + } + return parsed; + } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } @@ -226,6 +248,17 @@ protected Byte _parseByte(JsonParser jp, DeserializationContext ctxt) if (t == JsonToken.VALUE_NULL) { return (Byte) getNullValue(); } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Byte parsed = _parseByte(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'Byte' value but there was more than a single value in the array"); + } + return parsed; + } throw ctxt.mappingException(_valueClass, t); } @@ -260,6 +293,17 @@ protected Short _parseShort(JsonParser jp, DeserializationContext ctxt) if (t == JsonToken.VALUE_NULL) { return (Short) getNullValue(); } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Short parsed = _parseShort(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'Short' value but there was more than a single value in the array"); + } + return parsed; + } throw ctxt.mappingException(_valueClass, t); } @@ -310,6 +354,17 @@ protected final int _parseIntPrimitive(JsonParser jp, DeserializationContext ctx if (t == JsonToken.VALUE_NULL) { return 0; } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final int parsed = _parseIntPrimitive(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'int' value but there was more than a single value in the array"); + } + return parsed; + } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } @@ -347,6 +402,17 @@ protected final Integer _parseInteger(JsonParser jp, DeserializationContext ctxt if (t == JsonToken.VALUE_NULL) { return (Integer) getNullValue(); } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Integer parsed = _parseInteger(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'Integer' value but there was more than a single value in the array"); + } + return parsed; + } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } @@ -377,6 +443,17 @@ protected final Long _parseLong(JsonParser jp, DeserializationContext ctxt) thro if (t == JsonToken.VALUE_NULL) { return (Long) getNullValue(); } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Long parsed = _parseLong(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'Long' value but there was more than a single value in the array"); + } + return parsed; + } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } @@ -401,6 +478,17 @@ protected final long _parseLongPrimitive(JsonParser jp, DeserializationContext c if (t == JsonToken.VALUE_NULL) { return 0L; } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final long parsed = _parseLongPrimitive(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'long' value but there was more than a single value in the array"); + } + return parsed; + } throw ctxt.mappingException(_valueClass, t); } @@ -447,6 +535,17 @@ protected final Float _parseFloat(JsonParser jp, DeserializationContext ctxt) if (t == JsonToken.VALUE_NULL) { return (Float) getNullValue(); } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Float parsed = _parseFloat(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'Byte' value but there was more than a single value in the array"); + } + return parsed; + } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } @@ -489,6 +588,17 @@ protected final float _parseFloatPrimitive(JsonParser jp, DeserializationContext if (t == JsonToken.VALUE_NULL) { return 0.0f; } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final float parsed = _parseFloatPrimitive(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'float' value but there was more than a single value in the array"); + } + return parsed; + } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); } @@ -533,6 +643,17 @@ protected final Double _parseDouble(JsonParser jp, DeserializationContext ctxt) } if (t == JsonToken.VALUE_NULL) { return (Double) getNullValue(); + } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Double parsed = _parseDouble(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'Double' value but there was more than a single value in the array"); + } + return parsed; } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); @@ -577,6 +698,17 @@ protected final double _parseDoublePrimitive(JsonParser jp, DeserializationConte } if (t == JsonToken.VALUE_NULL) { return 0.0; + } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final double parsed = _parseDoublePrimitive(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'Byte' value but there was more than a single value in the array"); + } + return parsed; } // Otherwise, no can do: throw ctxt.mappingException(_valueClass, t); @@ -609,6 +741,17 @@ protected java.util.Date _parseDate(JsonParser jp, DeserializationContext ctxt) "not a valid representation (error: "+iae.getMessage()+")"); } } + // Issue#381 + if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Date parsed = _parseDate(jp, ctxt); + t = jp.nextToken(); + if (t != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'java.util.Date' value but there was more than a single value in the array"); + } + return parsed; + } throw ctxt.mappingException(_valueClass, t); } @@ -635,6 +778,16 @@ protected final static double parseDouble(String numStr) throws NumberFormatExce protected final String _parseString(JsonParser jp, DeserializationContext ctxt) throws IOException { + // Issue#381 + if (jp.getCurrentToken() == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final String parsed = _parseString(jp, ctxt); + if (jp.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'String' value but there was more than a single value in the array"); + } + return parsed; + } String value = jp.getValueAsString(); if (value != null) { return value; diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java index dcb01ded45..81b104650a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; @@ -23,6 +24,16 @@ public final class StringDeserializer extends StdScalarDeserializer @Override public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + // Issue#381 + if (jp.getCurrentToken() == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final String parsed = _parseString(jp, ctxt); + if (jp.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + "Attempted to unwrap single value array for single 'String' value but there was more than a single value in the array"); + } + return parsed; + } // 22-Sep-2012, tatu: For 2.1, use this new method, may force coercion: String text = jp.getValueAsString(); if (text != null) { diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestJdkTypes.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestJdkTypes.java index b84e987aed..53d99ca551 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestJdkTypes.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestJdkTypes.java @@ -386,4 +386,283 @@ public void testByteBuffer() throws Exception } assertEquals(0, result.remaining()); } + + // [Issue#381] + public void testSingleElementArray() throws Exception { + final int intTest = 932832; + final double doubleTest = 32.3234; + final long longTest = 2374237428374293423L; + final short shortTest = (short) intTest; + final float floatTest = 84.3743f; + final byte byteTest = (byte) 43; + final char charTest = 'c'; + + final ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + final int intValue = mapper.readValue(asArray(intTest), Integer.TYPE); + assertEquals(intTest, intValue); + final Integer integerWrapperValue = mapper.readValue(asArray(Integer.valueOf(intTest)), Integer.class); + assertEquals(Integer.valueOf(intTest), integerWrapperValue); + + final double doubleValue = mapper.readValue(asArray(doubleTest), Double.class); + assertEquals(doubleTest, doubleValue); + final Double doubleWrapperValue = mapper.readValue(asArray(Double.valueOf(doubleTest)), Double.class); + assertEquals(Double.valueOf(doubleTest), doubleWrapperValue); + + final long longValue = mapper.readValue(asArray(longTest), Long.TYPE); + assertEquals(longTest, longValue); + final Long longWrapperValue = mapper.readValue(asArray(Long.valueOf(longTest)), Long.class); + assertEquals(Long.valueOf(longTest), longWrapperValue); + + final short shortValue = mapper.readValue(asArray(shortTest), Short.TYPE); + assertEquals(shortTest, shortValue); + final Short shortWrapperValue = mapper.readValue(asArray(Short.valueOf(shortTest)), Short.class); + assertEquals(Short.valueOf(shortTest), shortWrapperValue); + + final float floatValue = mapper.readValue(asArray(floatTest), Float.TYPE); + assertEquals(floatTest, floatValue); + final Float floatWrapperValue = mapper.readValue(asArray(Float.valueOf(floatTest)), Float.class); + assertEquals(Float.valueOf(floatTest), floatWrapperValue); + + final byte byteValue = mapper.readValue(asArray(byteTest), Byte.TYPE); + assertEquals(byteTest, byteValue); + final Byte byteWrapperValue = mapper.readValue(asArray(Byte.valueOf(byteTest)), Byte.class); + assertEquals(Byte.valueOf(byteTest), byteWrapperValue); + + final char charValue = mapper.readValue(asArray(quote(String.valueOf(charTest))), Character.TYPE); + assertEquals(charTest, charValue); + final Character charWrapperValue = mapper.readValue(asArray(quote(String.valueOf(charTest))), Character.class); + assertEquals(Character.valueOf(charTest), charWrapperValue); + + final boolean booleanTrueValue = mapper.readValue(asArray(true), Boolean.TYPE); + assertTrue(booleanTrueValue); + + final boolean booleanFalseValue = mapper.readValue(asArray(false), Boolean.TYPE); + assertFalse(booleanFalseValue); + + final Boolean booleanWrapperTrueValue = mapper.readValue(asArray(Boolean.valueOf(true)), Boolean.class); + assertEquals(Boolean.TRUE, booleanWrapperTrueValue); + } + + private static String asArray(Object value) { + final String stringVal = value.toString(); + return new StringBuilder(stringVal.length() + 2).append("[").append(stringVal).append("]").toString(); + } + + public void testSingleElementArrayException() throws Exception { + try { + MAPPER.readValue("[42]", Integer.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + MAPPER.readValue("[42]", Integer.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + MAPPER.readValue("[42.273]", Double.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + MAPPER.readValue("[42.2723]", Double.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + MAPPER.readValue("[42342342342342]", Long.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + MAPPER.readValue("[42342342342342342]", Long.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + MAPPER.readValue("[42]", Short.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + MAPPER.readValue("[42]", Short.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + MAPPER.readValue("[327.2323]", Float.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + MAPPER.readValue("[82.81902]", Float.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + MAPPER.readValue("[22]", Byte.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + MAPPER.readValue("[22]", Byte.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + MAPPER.readValue("['d']", Character.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + MAPPER.readValue("['d']", Character.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + MAPPER.readValue("[true]", Boolean.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + MAPPER.readValue("[true]", Boolean.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + } + + public void testMultiValueArrayException() throws IOException { + final ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + try { + mapper.readValue("[42,42]", Integer.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + mapper.readValue("[42,42]", Integer.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + mapper.readValue("[42.273,42.273]", Double.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + mapper.readValue("[42.2723,42.273]", Double.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + mapper.readValue("[42342342342342,42342342342342]", Long.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + mapper.readValue("[42342342342342342,42342342342342]", Long.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + mapper.readValue("[42,42]", Short.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + mapper.readValue("[42,42]", Short.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + mapper.readValue("[327.2323,327.2323]", Float.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + mapper.readValue("[82.81902,327.2323]", Float.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + mapper.readValue("[22,23]", Byte.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + mapper.readValue("[22,23]", Byte.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + mapper.readValue(asArray(quote("c") + "," + quote("d")), Character.class); + + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + mapper.readValue(asArray(quote("c") + "," + quote("d")), Character.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + try { + mapper.readValue("[true,false]", Boolean.class); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + try { + mapper.readValue("[true,false]", Boolean.TYPE); + fail("Single value array didn't throw an exception when DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + } }