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/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java index c1e2d74675..c0f7c54b49 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -1211,6 +1211,14 @@ public Object deserializeFromArray(JsonParser jp, DeserializationContext ctxt) } catch (Exception e) { wrapInstantiationProblem(e, ctxt); } + } else if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + jp.nextToken(); + final Object 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; } throw ctxt.mappingException(getBeanClass()); } 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/TestEnumDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestEnumDeserialization.java index 3ca06ffa8c..764f93095f 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestEnumDeserialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestEnumDeserialization.java @@ -126,6 +126,7 @@ protected enum TestEnum324 */ protected final ObjectMapper MAPPER = new ObjectMapper(); + public void testSimple() throws Exception { @@ -374,4 +375,23 @@ public void testExceptionFromCreator() throws Exception verifyException(e, "foobar"); } } + + // [Issue#381] + public void testUnwrappedEnum() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + assertEquals(TestEnum.JACKSON, mapper.readValue("[" + quote("JACKSON") + "]", TestEnum.class)); + } + + public void testUnwrappedEnumException() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + try { + assertEquals(TestEnum.JACKSON, mapper.readValue("[" + quote("JACKSON") + "]", TestEnum.class)); + fail("Exception was not thrown on deserializing a single array element of type enum"); + } catch (JsonMappingException exp) { + //exception as thrown correctly + } + } } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestExceptionDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestExceptionDeserialization.java index 391ee83864..0342a0a92c 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestExceptionDeserialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestExceptionDeserialization.java @@ -97,4 +97,45 @@ public void testJDK7SuppressionProperty() throws IOException Exception exc = MAPPER.readValue("{\"suppressed\":[]}", IOException.class); assertNotNull(exc); } + + // [Issue#381] + public void testSingleValueArrayDeserialization() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + final IOException exp; + try { + throw new IOException("testing"); + } catch (IOException internal) { + exp = internal; + } + final String value = "[" + mapper.writeValueAsString(exp) + "]"; + + final IOException cloned = mapper.readValue(value, IOException.class); + assertEquals(exp.getMessage(), cloned.getMessage()); + + assertEquals(exp.getStackTrace().length, cloned.getStackTrace().length); + for (int i = 0; i < exp.getStackTrace().length; i ++) { + assertEquals(exp.getStackTrace()[i], cloned.getStackTrace()[i]); + } + } + + public void testSingleValueArrayDeserializationException() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + final IOException exp; + try { + throw new IOException("testing"); + } catch (IOException internal) { + exp = internal; + } + final String value = "[" + mapper.writeValueAsString(exp) + "]"; + + try { + mapper.readValue(value, IOException.class); + fail("Exception not thrown when attempting to deserialize an IOException wrapped in a single value array with UNWRAP_SINGLE_VALUE_ARRAYS disabled"); + } catch (JsonMappingException exp2) { + //Exception thrown correctly + } + } } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestGenerics.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestGenerics.java index b84b12e3f0..967a6e7efb 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestGenerics.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestGenerics.java @@ -76,6 +76,23 @@ public void testGenericWrapper() throws Exception SimpleBean bean = (SimpleBean) contents; assertEquals(13, bean.x); } + + public void testGenericWrapperWithSingleElementArray() throws Exception + { + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + Wrapper result = mapper.readValue + ("[{\"value\": [{ \"x\" : 13 }] }]", + new TypeReference>() { }); + assertNotNull(result); + assertEquals(Wrapper.class, result.getClass()); + Object contents = result.value; + assertNotNull(contents); + assertEquals(SimpleBean.class, contents.getClass()); + SimpleBean bean = (SimpleBean) contents; + assertEquals(13, bean.x); + } /** * Unit test for verifying that we can use different @@ -101,6 +118,28 @@ public void testMultipleWrappers() throws Exception ("{\"value\": 7}", new TypeReference>() { }); assertEquals(new Wrapper(7L), result3); } + + //[Issue#381] + public void testMultipleWrappersSingleValueArray() throws Exception + { + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + // First, numeric wrapper + Wrapper result = mapper.readValue + ("[{\"value\": [true]}]", new TypeReference>() { }); + assertEquals(new Wrapper(Boolean.TRUE), result); + + // Then string one + Wrapper result2 = mapper.readValue + ("[{\"value\": [\"abc\"]}]", new TypeReference>() { }); + assertEquals(new Wrapper("abc"), result2); + + // And then number + Wrapper result3 = mapper.readValue + ("[{\"value\": [7]}]", new TypeReference>() { }); + assertEquals(new Wrapper(7L), result3); + } /** * Unit test for verifying fix to [JACKSON-109]. @@ -121,4 +160,24 @@ public void testArrayOfGenericWrappers() throws Exception SimpleBean bean = (SimpleBean) contents; assertEquals(9, bean.x); } + + // [Issue#381] + public void testArrayOfGenericWrappersSingleValueArray() throws Exception + { + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + Wrapper[] result = mapper.readValue + ("[ {\"value\": [ { \"x\" : [ 9 ] } ] } ]", + new TypeReference[]>() { }); + assertNotNull(result); + assertEquals(Wrapper[].class, result.getClass()); + assertEquals(1, result.length); + Wrapper elem = result[0]; + Object contents = elem.value; + assertNotNull(contents); + assertEquals(SimpleBean.class, contents.getClass()); + SimpleBean bean = (SimpleBean) contents; + assertEquals(9, bean.x); + } } 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..9962dad2b0 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,285 @@ 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 { + final ObjectMapper mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + 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 + } + } } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestSimpleTypes.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestSimpleTypes.java index 3440f79a5f..666d090256 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestSimpleTypes.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestSimpleTypes.java @@ -75,6 +75,32 @@ public void testBooleanPrimitive() throws Exception assertNotNull(array); assertEquals(1, array.length); assertFalse(array[0]); + + // [Issue#381] + final ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + result = mapper.readValue(new StringReader("{\"v\":[true]}"), BooleanBean.class); + assertTrue(result._v); + + try { + mapper.readValue(new StringReader("[{\"v\":[true,true]}]"), BooleanBean.class); + fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled"); + } catch (JsonMappingException exp) { + //threw exception as required + } + + result = mapper.readValue(new StringReader("{\"v\":[null]}"), BooleanBean.class); + assertNotNull(result); + assertFalse(result._v); + + result = mapper.readValue(new StringReader("[{\"v\":[null]}]"), BooleanBean.class); + assertNotNull(result); + assertFalse(result._v); + + array = mapper.readValue(new StringReader("[ [ null ] ]"), boolean[].class); + assertNotNull(array); + assertEquals(1, array.length); + assertFalse(array[0]); } public void testIntPrimitive() throws Exception @@ -92,6 +118,40 @@ public void testIntPrimitive() throws Exception assertNotNull(array); assertEquals(1, array.length); assertEquals(0, array[0]); + + // [Issue#381] + final ObjectMapper mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + try { + mapper.readValue(new StringReader("{\"v\":[3]}"), IntBean.class); + fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled"); + } catch (JsonMappingException exp) { + //Correctly threw exception + } + + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + result = mapper.readValue(new StringReader("{\"v\":[3]}"), IntBean.class); + assertEquals(3, result._v); + + result = mapper.readValue(new StringReader("[{\"v\":[3]}]"), IntBean.class); + assertEquals(3, result._v); + + try { + mapper.readValue(new StringReader("[{\"v\":[3,3]}]"), IntBean.class); + fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled"); + } catch (JsonMappingException exp) { + //threw exception as required + } + + result = mapper.readValue(new StringReader("{\"v\":[null]}"), IntBean.class); + assertNotNull(result); + assertEquals(0, result._v); + + array = mapper.readValue(new StringReader("[ [ null ] ]"), int[].class); + assertNotNull(array); + assertEquals(1, array.length); + assertEquals(0, array[0]); } public void testDoublePrimitive() throws Exception @@ -111,6 +171,40 @@ public void testDoublePrimitive() throws Exception assertNotNull(array); assertEquals(1, array.length); assertEquals(0.0, array[0]); + + // [Issue#381] + final ObjectMapper mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + try { + mapper.readValue(new StringReader("{\"v\":[" + value + "]}"), DoubleBean.class); + fail("Did not throw exception when reading a value from a single value array with the UNWRAP_SINGLE_VALUE_ARRAYS feature disabled"); + } catch (JsonMappingException exp) { + //Correctly threw exception + } + + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + result = mapper.readValue(new StringReader("{\"v\":[" + value + "]}"), DoubleBean.class); + assertEquals(value, result._v); + + result = mapper.readValue(new StringReader("[{\"v\":[" + value + "]}]"), DoubleBean.class); + assertEquals(value, result._v); + + try { + mapper.readValue(new StringReader("[{\"v\":[" + value + "," + value + "]}]"), DoubleBean.class); + fail("Did not throw exception while reading a value from a multi value array with UNWRAP_SINGLE_VALUE_ARRAY feature enabled"); + } catch (JsonMappingException exp) { + //threw exception as required + } + + result = mapper.readValue(new StringReader("{\"v\":[null]}"), DoubleBean.class); + assertNotNull(result); + assertEquals(0d, result._v); + + array = mapper.readValue(new StringReader("[ [ null ] ]"), double[].class); + assertNotNull(array); + assertEquals(1, array.length); + assertEquals(0d, array[0]); } public void testDoublePrimitiveNonNumeric() throws Exception @@ -327,36 +421,130 @@ public void testSingleString() throws Exception String result = MAPPER.readValue(new StringReader("\""+value+"\""), String.class); assertEquals(value, result); } + + public void testSingleStringWrapped() throws Exception + { + final ObjectMapper mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + String value = "FOO!"; + try { + mapper.readValue(new StringReader("[\""+value+"\"]"), String.class); + fail("Exception not thrown when attempting to unwrap a single value 'String' array into a simple String"); + } catch (JsonMappingException exp) { + //exception thrown correctly + } + + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + try { + mapper.readValue(new StringReader("[\""+value+"\",\""+value+"\"]"), String.class); + fail("Exception not thrown when attempting to unwrap a single value 'String' array that contained more than one value into a simple String"); + } catch (JsonMappingException exp) { + //exception thrown correctly + } + + String result = mapper.readValue(new StringReader("[\""+value+"\"]"), String.class); + assertEquals(value, result); + } public void testNull() throws Exception { // null doesn't really have a type, fake by assuming Object Object result = MAPPER.readValue(" null", Object.class); assertNull(result); - } + } public void testClass() throws Exception { - Class result = MAPPER.readValue("\"java.lang.String\"", Class.class); + final ObjectMapper mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + Class result = mapper.readValue(quote(String.class.getName()), Class.class); + assertEquals(String.class, result); + + //[Issue#381] + try { + mapper.readValue("[" + quote(String.class.getName()) + "]", Class.class); + fail("Did not throw exception when UNWRAP_SINGLE_VALUE_ARRAYS feature was disabled and attempted to read a Class array containing one element"); + } catch (JsonMappingException exp) { + + } + + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + try { + mapper.readValue("[" + quote(Object.class.getName()) + "," + quote(Object.class.getName()) +"]", Class.class); + fail("Did not throw exception when UNWRAP_SINGLE_VALUE_ARRAYS feature was enabled and attempted to read a Class array containing two elements"); + } catch (JsonMappingException exp) { + + } + result = mapper.readValue("[" + quote(String.class.getName()) + "]", Class.class); assertEquals(String.class, result); } public void testBigDecimal() throws Exception { + final ObjectMapper mapper = objectMapper(); + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + BigDecimal value = new BigDecimal("0.001"); - BigDecimal result = MAPPER.readValue(new StringReader(value.toString()), BigDecimal.class); + BigDecimal result = mapper.readValue(value.toString(), BigDecimal.class); + assertEquals(value, result); + + //Issue#381 + try { + mapper.readValue("[" + value.toString() + "]", BigDecimal.class); + fail("Exception was not thrown when attempting to read a single value array of BigDecimal when UNWRAP_SINGLE_VALUE_ARRAYS feature is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + result = mapper.readValue("[" + value.toString() + "]", BigDecimal.class); assertEquals(value, result); + + try { + mapper.readValue("[" + value.toString() + "," + value.toString() + "]", BigDecimal.class); + fail("Exception was not thrown when attempting to read a muti value array of BigDecimal when UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } } public void testBigInteger() throws Exception { + final ObjectMapper mapper = objectMapper(); + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + BigInteger value = new BigInteger("-1234567890123456789012345567809"); - BigInteger result = MAPPER.readValue(new StringReader(value.toString()), BigInteger.class); + BigInteger result = mapper.readValue(new StringReader(value.toString()), BigInteger.class); assertEquals(value, result); + + //Issue#381 + try { + mapper.readValue("[" + value.toString() + "]", BigInteger.class); + fail("Exception was not thrown when attempting to read a single value array of BigInteger when UNWRAP_SINGLE_VALUE_ARRAYS feature is disabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } + + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + result = mapper.readValue("[" + value.toString() + "]", BigInteger.class); + assertEquals(value, result); + + try { + mapper.readValue("[" + value.toString() + "," + value.toString() + "]", BigInteger.class); + fail("Exception was not thrown when attempting to read a muti value array of BigInteger when UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled"); + } catch (JsonMappingException exp) { + //Exception was thrown correctly + } } public void testUUID() throws Exception { + final ObjectMapper mapper = objectMapper(); + final String NULL_UUID = "00000000-0000-0000-0000-000000000000"; // first, couple of generated UUIDs: for (String value : new String[] { @@ -367,9 +555,31 @@ public void testUUID() throws Exception "82994ac2-7b23-49f2-8cc5-e24cf6ed77be", "00000007-0000-0000-0000-000000000000" }) { + + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + UUID uuid = UUID.fromString(value); assertEquals(uuid, - MAPPER.readValue(quote(value), UUID.class)); + mapper.readValue(quote(value), UUID.class)); + + try { + mapper.readValue("[" + quote(value) + "]", UUID.class); + fail("Exception was not thrown when UNWRAP_SINGLE_VALUE_ARRAYS is disabled and attempted to read a single value array as a single element"); + } catch (JsonMappingException exp) { + //Exception thrown successfully + } + + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + assertEquals(uuid, + mapper.readValue("[" + quote(value) + "]", UUID.class)); + + try { + mapper.readValue("[" + quote(value) + "," + quote(value) + "]", UUID.class); + fail("Exception was not thrown when UNWRAP_SINGLE_VALUE_ARRAYS is enabled and attempted to read a multi value array as a single element"); + } catch (JsonMappingException exp) { + //Exception thrown successfully + } } // then use templating; note that these are not exactly valid UUIDs // wrt spec (type bits etc), but JDK UUID should deal ok @@ -379,13 +589,13 @@ public void testUUID() throws Exception for (int i = 0; i < chars.length(); ++i) { String value = TEMPL.replace('0', chars.charAt(i)); assertEquals(UUID.fromString(value).toString(), - MAPPER.readValue(quote(value), UUID.class).toString()); + mapper.readValue(quote(value), UUID.class).toString()); } // also: see if base64 encoding works as expected String base64 = Base64Variants.getDefaultVariant().encode(new byte[16]); assertEquals(UUID.fromString(NULL_UUID), - MAPPER.readValue(quote(base64), UUID.class)); + mapper.readValue(quote(base64), UUID.class)); } public void testUUIDAux() throws Exception @@ -441,8 +651,29 @@ public void testURL() throws Exception public void testURI() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + URI value = new URI("http://foo.com"); - assertEquals(value, MAPPER.readValue("\""+value.toString()+"\"", URI.class)); + assertEquals(value, mapper.readValue("\""+value.toString()+"\"", URI.class)); + + //[Issue#381] + mapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + try { + assertEquals(value, mapper.readValue("[\""+value.toString()+"\"]", URI.class)); + fail("Did not throw exception for single value array when UNWRAP_SINGLE_VALUE_ARRAYS is disabled"); + } catch (JsonMappingException exp) { + //exception thrown successfully + } + + try { + assertEquals(value, mapper.readValue("[\""+value.toString()+"\",\""+value.toString()+"\"]", URI.class)); + fail("Did not throw exception for single value array when there were multiple values"); + } catch (JsonMappingException exp) { + //exception thrown successfully + } + + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + assertEquals(value, mapper.readValue("[\""+value.toString()+"\"]", URI.class)); } /* diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestTimestampDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestTimestampDeserialization.java index 44959aa89f..295cdad804 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestTimestampDeserialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestTimestampDeserialization.java @@ -25,6 +25,23 @@ public void testTimestampUtil() throws Exception assertEquals("Date: expect "+value+" ("+value.getTime()+"), got "+result+" ("+result.getTime()+")", value.getTime(), result.getTime()); } + + public void testTimestampUtilSingleElementArray() throws Exception + { + final ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS); + + long now = System.currentTimeMillis(); + java.sql.Timestamp value = new java.sql.Timestamp(now); + + // First from long + assertEquals(value, mapper.readValue("["+now+"]", java.sql.Timestamp.class)); + + String dateStr = serializeTimestampAsString(value); + java.sql.Timestamp result = mapper.readValue("[\""+dateStr+"\"]", java.sql.Timestamp.class); + + assertEquals("Date: expect "+value+" ("+value.getTime()+"), got "+result+" ("+result.getTime()+")", value.getTime(), result.getTime()); + } /* /**********************************************************