VYPR
High severityNVD Advisory· Published Oct 2, 2022· Updated Aug 3, 2024

CVE-2022-42003

CVE-2022-42003

Description

In FasterXML jackson-databind before versions 2.13.4.1 and 2.12.17.1, resource exhaustion can occur because of a lack of a check in primitive value deserializers to avoid deep wrapper array nesting, when the UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Resource exhaustion in FasterXML jackson-databind via deep wrapper array nesting when UNWRAP_SINGLE_VALUE_ARRAYS is enabled.

Description

CVE-2022-42003 is a resource exhaustion vulnerability in FasterXML jackson-databind affecting versions before 2.13.4.1 and 2.12.17.1. The root cause is a missing check in primitive value deserializers to prevent deep wrapper array nesting when the UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled. An attacker can craft a JSON payload with deeply nested arrays, causing excessive CPU and memory consumption [2].

Exploitation

To exploit this vulnerability, the attacker must be able to send malicious JSON input to a service using jackson-databind with the UNWRAP_SINGLE_VALUE_ARRAYS deserialization feature enabled. No authentication is required beyond network access to the service. The vulnerability is triggered during deserialization of primitive values (e.g., boolean, byte, short) when the input contains deeply nested arrays [3].

Impact

Successful exploitation results in resource exhaustion, leading to denial of service (DoS) as the application becomes unresponsive due to high CPU and memory usage. This can disrupt normal operations and potentially affect other services sharing the same resources.

Mitigation

The vulnerability is fixed in jackson-databind versions 2.13.4.1 and 2.12.17.1. Patches include adding a check in the handleNestedArrayForSingle method to limit nesting depth (commits d78d00e and 7ba9ac5) [3][4]. Users are advised to upgrade to the patched versions. As a workaround, the UNWRAP_SINGLE_VALUE_ARRAYS feature can be disabled.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
com.fasterxml.jackson.core:jackson-databindMaven
>= 2.4.0-rc1, < 2.12.7.12.12.7.1
com.fasterxml.jackson.core:jackson-databindMaven
>= 2.13.0, < 2.13.4.22.13.4.2

Affected products

47

Patches

6
cd090979b7ea

backport Fix #3590 and Fix #3582 (#3622)

https://github.com/FasterXML/jackson-databindCesar HernandezOct 13, 2022via ghsa
6 files changed · +203 14
  • release-notes/VERSION-2.x+9 1 modified
    @@ -4,6 +4,14 @@ Project: jackson-databind
     === Releases === 
     ------------------------------------------------------------------------
     
    +2.12.7.1 (not yest released)
    +
    +#3582: Add check in `BeanDeserializer._deserializeFromArray()` to prevent
    +  use of deeply nested arrays [CVE-2022-42004]
    +
    +#3590: Add check in primitive value deserializers to avoid deep wrapper array
    +  nesting wrt `UNWRAP_SINGLE_VALUE_ARRAYS` [CVE-2022-42003]
    +
     2.12.7 (26-May-2022)
     
     #2816: Optimize UntypedObjectDeserializer wrt recursion [CVE-2020-36518]
    @@ -16,7 +24,7 @@ Project: jackson-databind
     #3305: ObjectMapper serializes `CharSequence` subtypes as POJO instead of
       as String (JDK 15+)
      (reported by stevenupton@github; fix suggested by Sergey C)
    -#3328: Possible DoS issue
    +#3328: Possible DoS if using JDK serialization to serialize JsonNode
     
     2.12.5 (27-Aug-2021)
     
    
  • src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java+10 0 modified
    @@ -8,6 +8,7 @@
     import com.fasterxml.jackson.databind.cfg.CoercionAction;
     import com.fasterxml.jackson.databind.deser.impl.*;
     import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
    +import com.fasterxml.jackson.databind.util.ClassUtil;
     import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil;
     import com.fasterxml.jackson.databind.util.NameTransformer;
     import com.fasterxml.jackson.databind.util.TokenBuffer;
    @@ -630,6 +631,15 @@ protected Object _deserializeFromArray(JsonParser p, DeserializationContext ctxt
                     return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
                 }
                 if (unwrap) {
    +                // 23-Aug-2022, tatu: To prevent unbounded nested arrays, we better
    +                //   check there is NOT another START_ARRAY lurking there..
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    JavaType targetType = getValueType(ctxt);
    +                    return ctxt.handleUnexpectedToken(targetType, JsonToken.START_ARRAY, p,
    +"Cannot deserialize value of type %s from deeply-nested JSON Array: only single wrapper allowed with `%s`",
    +                            ClassUtil.getTypeDescription(targetType),
    +                                    "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
    +                }
                     final Object value = deserialize(p, ctxt);
                     if (p.nextToken() != JsonToken.END_ARRAY) {
                         handleMissingEndArrayForSingle(p, ctxt);
    
  • src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java+3 1 modified
    @@ -318,8 +318,10 @@ public enum DeserializationFeature implements ConfigFeature
          * 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.
          * <p>
    +     * NOTE: only <b>single</b> wrapper Array is allowed: if multiple attempted, exception
    +     * will be thrown.
          * 
    -     * Feature is disabled by default
    +     * Feature is disabled by default.
          * @since 2.4
          */
         UNWRAP_SINGLE_VALUE_ARRAYS(false),
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java+40 12 modified
    @@ -357,12 +357,8 @@ protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt)
             // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
             //   either supporting nested arrays, or to cause infinite looping.
             if (p.hasToken(JsonToken.START_ARRAY)) {
    -            String msg = String.format(
    -"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
    -                    ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
    -                    "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
                 @SuppressWarnings("unchecked")
    -            T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
    +            T result = (T) handleNestedArrayForSingle(p, ctxt);
                 return result;
             }
             return (T) deserialize(p, ctxt);
    @@ -413,7 +409,9 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont
             case JsonTokenId.ID_START_ARRAY:
                 // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (boolean) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final boolean parsed = _parseBooleanPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -582,7 +580,9 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct
             case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion?
                 // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (byte) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final byte parsed = _parseBytePrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -650,7 +650,9 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext
             case JsonTokenId.ID_START_ARRAY:
                 // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (short) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final short parsed = _parseShortPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -715,7 +717,9 @@ protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (int) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final int parsed = _parseIntPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -842,7 +846,9 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (long) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final long parsed = _parseLongPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -953,7 +959,9 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (float) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final float parsed = _parseFloatPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -1058,7 +1066,9 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (double) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final double parsed = _parseDoublePrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -1214,6 +1224,9 @@ protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContex
                     default:
                     }
                 } else if (unwrap) {
    +                if (t == JsonToken.START_ARRAY) {
    +                    return (java.util.Date) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final Date parsed = _parseDate(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -1990,6 +2003,21 @@ protected void handleMissingEndArrayForSingle(JsonParser p, DeserializationConte
             //     but for now just fall through
         }
     
    +    /**
    +     * Helper method called when detecting a deep(er) nesting of Arrays when trying
    +     * to unwrap value for {@code DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS}.
    +     *
    +     * @since 2.14
    +     */
    +    protected Object handleNestedArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
    +    {
    +        String msg = String.format(
    +"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
    +                ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
    +                "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
    +        return ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
    +    }
    +
         protected void _verifyEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
         {
             JsonToken t = p.nextToken();
    
  • src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3582Test.java+46 0 added
    @@ -0,0 +1,46 @@
    +package com.fasterxml.jackson.databind.deser.dos;
    +
    +import com.fasterxml.jackson.databind.*;
    +import com.fasterxml.jackson.databind.exc.MismatchedInputException;
    +
    +public class DeepArrayWrappingForDeser3582Test extends BaseMapTest
    +{
    +    // 23-Aug-2022, tatu: Before fix, failed with 5000
    +    private final static int TOO_DEEP_NESTING = 9999;
    +
    +    private final ObjectMapper MAPPER = jsonMapperBuilder()
    +            .enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
    +            .build();
    +
    +    public void testArrayWrapping() throws Exception
    +    {
    +        final String doc = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ", "{}");
    +        try {
    +            MAPPER.readValue(doc, Point.class);
    +            fail("Should not pass");
    +        } catch (MismatchedInputException e) {
    +            verifyException(e, "Cannot deserialize");
    +            verifyException(e, "nested JSON Array");
    +            verifyException(e, "only single");
    +        }
    +    }
    +
    +    private String _nestedDoc(int nesting, String open, String close, String content) {
    +        StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length()));
    +        for (int i = 0; i < nesting; ++i) {
    +            sb.append(open);
    +            if ((i & 31) == 0) {
    +                sb.append("\n");
    +            }
    +        }
    +        sb.append("\n").append(content).append("\n");
    +        for (int i = 0; i < nesting; ++i) {
    +            sb.append(close);
    +            if ((i & 31) == 0) {
    +                sb.append("\n");
    +            }
    +        }
    +        return sb.toString();
    +    }
    +
    +}
    
  • src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3590Test.java+95 0 added
    @@ -0,0 +1,95 @@
    +package com.fasterxml.jackson.databind.deser.dos;
    +
    +import java.util.Date;
    +
    +import com.fasterxml.jackson.databind.*;
    +import com.fasterxml.jackson.databind.exc.MismatchedInputException;
    +
    +public class DeepArrayWrappingForDeser3590Test extends BaseMapTest
    +{
    +    // 05-Sep-2022, tatu: Before fix, failed with 5000
    +    private final static int TOO_DEEP_NESTING = 9999;
    +
    +    private final ObjectMapper MAPPER = jsonMapperBuilder()
    +            .enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
    +            .build();
    +
    +    private final static String TOO_DEEP_DOC = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ", "123");
    +
    +    public void testArrayWrappingForBoolean() throws Exception
    +    {
    +        _testArrayWrappingFor(Boolean.class);
    +        _testArrayWrappingFor(Boolean.TYPE);
    +    }
    +
    +    public void testArrayWrappingForByte() throws Exception
    +    {
    +        _testArrayWrappingFor(Byte.class);
    +        _testArrayWrappingFor(Byte.TYPE);
    +    }
    +
    +    public void testArrayWrappingForShort() throws Exception
    +    {
    +        _testArrayWrappingFor(Short.class);
    +        _testArrayWrappingFor(Short.TYPE);
    +    }
    +
    +    public void testArrayWrappingForInt() throws Exception
    +    {
    +        _testArrayWrappingFor(Integer.class);
    +        _testArrayWrappingFor(Integer.TYPE);
    +    }
    +
    +    public void testArrayWrappingForLong() throws Exception
    +    {
    +        _testArrayWrappingFor(Long.class);
    +        _testArrayWrappingFor(Long.TYPE);
    +    }
    +
    +    public void testArrayWrappingForFloat() throws Exception
    +    {
    +        _testArrayWrappingFor(Float.class);
    +        _testArrayWrappingFor(Float.TYPE);
    +    }
    +
    +    public void testArrayWrappingForDouble() throws Exception
    +    {
    +        _testArrayWrappingFor(Double.class);
    +        _testArrayWrappingFor(Double.TYPE);
    +    }
    +
    +    public void testArrayWrappingForDate() throws Exception
    +    {
    +        _testArrayWrappingFor(Date.class);
    +    }
    +
    +    private void _testArrayWrappingFor(Class<?> cls) throws Exception
    +    {
    +        try {
    +            MAPPER.readValue(TOO_DEEP_DOC, cls);
    +            fail("Should not pass");
    +        } catch (MismatchedInputException e) {
    +            verifyException(e, "Cannot deserialize");
    +            verifyException(e, "nested Arrays not allowed");
    +        }
    +    }
    +
    +    private static String _nestedDoc(int nesting, String open, String close, String content) {
    +        StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length()));
    +        for (int i = 0; i < nesting; ++i) {
    +            sb.append(open);
    +            if ((i & 31) == 0) {
    +                sb.append("\n");
    +            }
    +        }
    +        sb.append("\n").append(content).append("\n");
    +        for (int i = 0; i < nesting; ++i) {
    +            sb.append(close);
    +            if ((i & 31) == 0) {
    +                sb.append("\n");
    +            }
    +        }
    +        return sb.toString();
    +    }
    +
    +}
    
2c4a601c626f

[2.13.x] Add check in primitive value deserializers to avoid deep wrapper array nesting wrt UNWRAP_SINGLE_VALUE_ARRAYS [CVE-2022-42003] (#3621)

https://github.com/FasterXML/jackson-databindRichard ZowallaOct 12, 2022via ghsa
3 files changed · +140 12
  • release-notes/VERSION-2.x+5 0 modified
    @@ -4,6 +4,11 @@ Project: jackson-databind
     === Releases === 
     ------------------------------------------------------------------------
     
    +2.13.4.1 (not yet released)
    +
    +#3590: Add check in primitive value deserializers to avoid deep wrapper array
    +  nesting wrt `UNWRAP_SINGLE_VALUE_ARRAYS` [CVE-2022-42003]
    +
     2.13.4 (03-Sep-2022)
     
     #3275: JDK 16 Illegal reflective access for `Throwable.setCause()` with
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java+40 12 modified
    @@ -357,12 +357,8 @@ protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt)
             // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
             //   either supporting nested arrays, or to cause infinite looping.
             if (p.hasToken(JsonToken.START_ARRAY)) {
    -            String msg = String.format(
    -"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
    -                    ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
    -                    "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
                 @SuppressWarnings("unchecked")
    -            T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
    +            T result = (T) handleNestedArrayForSingle(p, ctxt);
                 return result;
             }
             return (T) deserialize(p, ctxt);
    @@ -413,7 +409,9 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont
             case JsonTokenId.ID_START_ARRAY:
                 // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (boolean) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final boolean parsed = _parseBooleanPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -582,7 +580,9 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct
             case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion?
                 // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (byte) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final byte parsed = _parseBytePrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -652,7 +652,9 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext
             case JsonTokenId.ID_START_ARRAY:
                 // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (short) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final short parsed = _parseShortPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -719,7 +721,9 @@ protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (int) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final int parsed = _parseIntPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -870,7 +874,9 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (long) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final long parsed = _parseLongPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -995,7 +1001,9 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (float) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final float parsed = _parseFloatPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -1102,7 +1110,9 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (double) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final double parsed = _parseDoublePrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -1259,6 +1269,9 @@ protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContex
                     default:
                     }
                 } else if (unwrap) {
    +                if (t == JsonToken.START_ARRAY) {
    +                    return (java.util.Date) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final Date parsed = _parseDate(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -2039,6 +2052,21 @@ protected void handleMissingEndArrayForSingle(JsonParser p, DeserializationConte
             //     but for now just fall through
         }
     
    +    /**
    +     * Helper method called when detecting a deep(er) nesting of Arrays when trying
    +     * to unwrap value for {@code DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS}.
    +     *
    +     * @since 2.13.4.1
    +     */
    +    protected Object handleNestedArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
    +    {
    +        String msg = String.format(
    +"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
    +                ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
    +                "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
    +        return ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
    +    }
    +
         protected void _verifyEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
         {
             JsonToken t = p.nextToken();
    
  • src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3590Test.java+95 0 added
    @@ -0,0 +1,95 @@
    +package com.fasterxml.jackson.databind.deser.dos;
    +
    +import java.util.Date;
    +
    +import com.fasterxml.jackson.databind.*;
    +import com.fasterxml.jackson.databind.exc.MismatchedInputException;
    +
    +public class DeepArrayWrappingForDeser3590Test extends BaseMapTest
    +{
    +    // 05-Sep-2022, tatu: Before fix, failed with 5000
    +    private final static int TOO_DEEP_NESTING = 9999;
    +
    +    private final ObjectMapper MAPPER = jsonMapperBuilder()
    +            .enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
    +            .build();
    +
    +    private final static String TOO_DEEP_DOC = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ", "123");
    +
    +    public void testArrayWrappingForBoolean() throws Exception
    +    {
    +        _testArrayWrappingFor(Boolean.class);
    +        _testArrayWrappingFor(Boolean.TYPE);
    +    }
    +
    +    public void testArrayWrappingForByte() throws Exception
    +    {
    +        _testArrayWrappingFor(Byte.class);
    +        _testArrayWrappingFor(Byte.TYPE);
    +    }
    +
    +    public void testArrayWrappingForShort() throws Exception
    +    {
    +        _testArrayWrappingFor(Short.class);
    +        _testArrayWrappingFor(Short.TYPE);
    +    }
    +
    +    public void testArrayWrappingForInt() throws Exception
    +    {
    +        _testArrayWrappingFor(Integer.class);
    +        _testArrayWrappingFor(Integer.TYPE);
    +    }
    +
    +    public void testArrayWrappingForLong() throws Exception
    +    {
    +        _testArrayWrappingFor(Long.class);
    +        _testArrayWrappingFor(Long.TYPE);
    +    }
    +
    +    public void testArrayWrappingForFloat() throws Exception
    +    {
    +        _testArrayWrappingFor(Float.class);
    +        _testArrayWrappingFor(Float.TYPE);
    +    }
    +
    +    public void testArrayWrappingForDouble() throws Exception
    +    {
    +        _testArrayWrappingFor(Double.class);
    +        _testArrayWrappingFor(Double.TYPE);
    +    }
    +
    +    public void testArrayWrappingForDate() throws Exception
    +    {
    +        _testArrayWrappingFor(Date.class);
    +    }
    +
    +    private void _testArrayWrappingFor(Class<?> cls) throws Exception
    +    {
    +        try {
    +            MAPPER.readValue(TOO_DEEP_DOC, cls);
    +            fail("Should not pass");
    +        } catch (MismatchedInputException e) {
    +            verifyException(e, "Cannot deserialize");
    +            verifyException(e, "nested Arrays not allowed");
    +        }
    +    }
    +
    +    private static String _nestedDoc(int nesting, String open, String close, String content) {
    +        StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length()));
    +        for (int i = 0; i < nesting; ++i) {
    +            sb.append(open);
    +            if ((i & 31) == 0) {
    +                sb.append("\n");
    +            }
    +        }
    +        sb.append("\n").append(content).append("\n");
    +        for (int i = 0; i < nesting; ++i) {
    +            sb.append(close);
    +            if ((i & 31) == 0) {
    +                sb.append("\n");
    +            }
    +        }
    +        return sb.toString();
    +    }
    +
    +}
    
d78d00ee7b52

Fix #3590

https://github.com/FasterXML/jackson-databindTatu SalorantaSep 7, 2022via ghsa
3 files changed · +137 12
  • release-notes/VERSION-2.x+2 0 modified
    @@ -57,6 +57,8 @@ Project: jackson-databind
     #3559: Support `null`-valued `Map` fields with "any setter"
     #3568: Change `JsonNode.with(String)` and `withArray(String)` to consider
       argument as `JsonPointer` if valid expression
    +#3590: Add check in primitive value deserializers to avoid deep wrapper array
    +  nesting wrt `UNWRAP_SINGLE_VALUE_ARRAYS`
     
     2.13.4 (03-Sep-2022)
     
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java+40 12 modified
    @@ -357,12 +357,8 @@ protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt)
             // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
             //   either supporting nested arrays, or to cause infinite looping.
             if (p.hasToken(JsonToken.START_ARRAY)) {
    -            String msg = String.format(
    -"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
    -                    ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
    -                    "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
                 @SuppressWarnings("unchecked")
    -            T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
    +            T result = (T) handleNestedArrayForSingle(p, ctxt);
                 return result;
             }
             return (T) deserialize(p, ctxt);
    @@ -413,7 +409,9 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont
             case JsonTokenId.ID_START_ARRAY:
                 // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (boolean) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final boolean parsed = _parseBooleanPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -582,7 +580,9 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct
             case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion?
                 // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (byte) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final byte parsed = _parseBytePrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -652,7 +652,9 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext
             case JsonTokenId.ID_START_ARRAY:
                 // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (short) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final short parsed = _parseShortPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -719,7 +721,9 @@ protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (int) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final int parsed = _parseIntPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -870,7 +874,9 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (long) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final long parsed = _parseLongPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -1003,7 +1009,9 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (float) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final float parsed = _parseFloatPrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -1132,7 +1140,9 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
                 break;
             case JsonTokenId.ID_START_ARRAY:
                 if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                p.nextToken();
    +                if (p.nextToken() == JsonToken.START_ARRAY) {
    +                    return (double) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final double parsed = _parseDoublePrimitive(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -1313,6 +1323,9 @@ protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContex
                     default:
                     }
                 } else if (unwrap) {
    +                if (t == JsonToken.START_ARRAY) {
    +                    return (java.util.Date) handleNestedArrayForSingle(p, ctxt);
    +                }
                     final Date parsed = _parseDate(p, ctxt);
                     _verifyEndArrayForSingle(p, ctxt);
                     return parsed;
    @@ -2109,6 +2122,21 @@ protected void handleMissingEndArrayForSingle(JsonParser p, DeserializationConte
             //     but for now just fall through
         }
     
    +    /**
    +     * Helper method called when detecting a deep(er) nesting of Arrays when trying
    +     * to unwrap value for {@code DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS}.
    +     *
    +     * @since 2.14
    +     */
    +    protected Object handleNestedArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
    +    {
    +        String msg = String.format(
    +"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
    +                ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
    +                "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
    +        return ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
    +    }
    +
         protected void _verifyEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
         {
             JsonToken t = p.nextToken();
    
  • src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3590Test.java+95 0 added
    @@ -0,0 +1,95 @@
    +package com.fasterxml.jackson.databind.deser.dos;
    +
    +import java.util.Date;
    +
    +import com.fasterxml.jackson.databind.*;
    +import com.fasterxml.jackson.databind.exc.MismatchedInputException;
    +
    +public class DeepArrayWrappingForDeser3590Test extends BaseMapTest
    +{
    +    // 05-Sep-2022, tatu: Before fix, failed with 5000
    +    private final static int TOO_DEEP_NESTING = 9999;
    +
    +    private final ObjectMapper MAPPER = jsonMapperBuilder()
    +            .enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
    +            .build();
    +
    +    private final static String TOO_DEEP_DOC = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ", "123");
    +
    +    public void testArrayWrappingForBoolean() throws Exception
    +    {
    +        _testArrayWrappingFor(Boolean.class);
    +        _testArrayWrappingFor(Boolean.TYPE);
    +    }
    +
    +    public void testArrayWrappingForByte() throws Exception
    +    {
    +        _testArrayWrappingFor(Byte.class);
    +        _testArrayWrappingFor(Byte.TYPE);
    +    }
    +
    +    public void testArrayWrappingForShort() throws Exception
    +    {
    +        _testArrayWrappingFor(Short.class);
    +        _testArrayWrappingFor(Short.TYPE);
    +    }
    +
    +    public void testArrayWrappingForInt() throws Exception
    +    {
    +        _testArrayWrappingFor(Integer.class);
    +        _testArrayWrappingFor(Integer.TYPE);
    +    }
    +
    +    public void testArrayWrappingForLong() throws Exception
    +    {
    +        _testArrayWrappingFor(Long.class);
    +        _testArrayWrappingFor(Long.TYPE);
    +    }
    +
    +    public void testArrayWrappingForFloat() throws Exception
    +    {
    +        _testArrayWrappingFor(Float.class);
    +        _testArrayWrappingFor(Float.TYPE);
    +    }
    +
    +    public void testArrayWrappingForDouble() throws Exception
    +    {
    +        _testArrayWrappingFor(Double.class);
    +        _testArrayWrappingFor(Double.TYPE);
    +    }
    +
    +    public void testArrayWrappingForDate() throws Exception
    +    {
    +        _testArrayWrappingFor(Date.class);
    +    }
    +
    +    private void _testArrayWrappingFor(Class<?> cls) throws Exception
    +    {
    +        try {
    +            MAPPER.readValue(TOO_DEEP_DOC, cls);
    +            fail("Should not pass");
    +        } catch (MismatchedInputException e) {
    +            verifyException(e, "Cannot deserialize");
    +            verifyException(e, "nested Arrays not allowed");
    +        }
    +    }
    +
    +    private static String _nestedDoc(int nesting, String open, String close, String content) {
    +        StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length()));
    +        for (int i = 0; i < nesting; ++i) {
    +            sb.append(open);
    +            if ((i & 31) == 0) {
    +                sb.append("\n");
    +            }
    +        }
    +        sb.append("\n").append(content).append("\n");
    +        for (int i = 0; i < nesting; ++i) {
    +            sb.append(close);
    +            if ((i & 31) == 0) {
    +                sb.append("\n");
    +            }
    +        }
    +        return sb.toString();
    +    }
    +
    +}
    
7ba9ac5b87a9

Yet more refactoring to figure out coercion for empty String

https://github.com/FasterXML/jackson-databindTatu SalorantaMay 31, 2020via ghsa
3 files changed · +99 109
  • src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java+1 1 modified
    @@ -1529,7 +1529,7 @@ public Object deserializeFromEmbedded(JsonParser p, DeserializationContext ctxt)
         /**
          * @since 2.9
          */
    -    private final JsonDeserializer<Object> _delegateDeserializer() {
    +    protected final JsonDeserializer<Object> _delegateDeserializer() {
             JsonDeserializer<Object> deser = _delegateDeserializer;
             if (deser == null) {
                 deser = _arrayDelegateDeserializer;
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/MapEntryDeserializer.java+2 5 modified
    @@ -175,13 +175,10 @@ public Map.Entry<Object,Object> deserialize(JsonParser p, DeserializationContext
         {
             // Ok: must point to START_OBJECT, FIELD_NAME or END_OBJECT
             JsonToken t = p.currentToken();
    -        if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) {
    -            // String may be ok however:
    -            // slightly redundant (since String was passed above), but
    -            return _deserializeFromEmpty(p, ctxt);
    -        }
             if (t == JsonToken.START_OBJECT) {
                 t = p.nextToken();
    +        } else if (t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) {
    +            return _deserializeFromEmpty(p, ctxt);
             }
             if (t != JsonToken.FIELD_NAME) {
                 if (t == JsonToken.END_OBJECT) {
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java+96 103 modified
    @@ -142,7 +142,7 @@ protected boolean isDefaultDeserializer(JsonDeserializer<?> deserializer) {
         protected boolean isDefaultKeyDeserializer(KeyDeserializer keyDeser) {
             return ClassUtil.isJacksonStdImpl(keyDeser);
         }
    -    
    +
         /*
         /**********************************************************
         /* Partial JsonDeserializer implementation 
    @@ -160,6 +160,101 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
             return typeDeserializer.deserializeTypedFromAny(p, ctxt);
         }
     
    +    /*
    +    /**********************************************************
    +    /* High-level handling of secondary input shapes (with
    +    /* possible coercion)
    +    /**********************************************************
    +     */
    +
    +    /**
    +     * Helper method that allows easy support for array-related {@link DeserializationFeature}s
    +     * `ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT` and `UNWRAP_SINGLE_VALUE_ARRAYS`: checks for either
    +     * empty array, or single-value array-wrapped value (respectively), and either reports
    +     * an exception (if no match, or feature(s) not enabled), or returns appropriate
    +     * result value.
    +     *<p>
    +     * This method should NOT be called if Array representation is explicitly supported
    +     * for type: it should only be called in case it is otherwise unrecognized.
    +     *<p>
    +     * NOTE: in case of unwrapped single element, will handle actual decoding
    +     * by calling {@link #_deserializeWrappedValue}, which by default calls
    +     * {@link #deserialize(JsonParser, DeserializationContext)}.
    +     *
    +     * @since 2.9
    +     */
    +    protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
    +    {
    +        JsonToken t;
    +        if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) {
    +            t = p.nextToken();
    +            if (t == JsonToken.END_ARRAY) {
    +                if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
    +                    return getNullValue(ctxt);
    +                }
    +            }
    +            if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    +                final T parsed = deserialize(p, ctxt);
    +                if (p.nextToken() != JsonToken.END_ARRAY) {
    +                    handleMissingEndArrayForSingle(p, ctxt);
    +                }
    +                return parsed;
    +            }
    +        } else {
    +            t = p.currentToken();
    +        }
    +        @SuppressWarnings("unchecked")
    +        T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, null);
    +        return result;
    +    }
    +
    +    /**
    +     * Helper method that may be used to support fallback for Empty String / Empty Array
    +     * non-standard representations; usually for things serialized as JSON Objects.
    +     *
    +     * @since 2.5
    +     */
    +    @SuppressWarnings("unchecked")
    +    protected T _deserializeFromEmpty(JsonParser p, DeserializationContext ctxt)
    +        throws IOException
    +    {
    +        JsonToken t = p.currentToken();
    +        if (t == JsonToken.START_ARRAY) {
    +            if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
    +                t = p.nextToken();
    +                if (t == JsonToken.END_ARRAY) {
    +                    return null;
    +                }
    +                return (T) ctxt.handleUnexpectedToken(handledType(), p);
    +            }
    +        }
    +        return (T) ctxt.handleUnexpectedToken(handledType(), p);
    +    }
    +
    +    /**
    +     * Helper called to support {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}:
    +     * default implementation simply calls
    +     * {@link #deserialize(JsonParser, DeserializationContext)},
    +     * but handling may be overridden.
    +     *
    +     * @since 2.9
    +     */
    +    protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt) throws IOException
    +    {
    +        // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
    +        //   either supporting nested arrays, or to cause infinite looping.
    +        if (p.hasToken(JsonToken.START_ARRAY)) {
    +            String msg = String.format(
    +"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
    +                    ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
    +                    "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
    +            @SuppressWarnings("unchecked")
    +            T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
    +            return result;
    +        }
    +        return (T) deserialize(p, ctxt);
    +    }
    +
         /*
         /**********************************************************
         /* Helper methods for sub-classes, parsing: while mostly
    @@ -616,36 +711,6 @@ protected final String _parseString(JsonParser p, DeserializationContext ctxt) t
             return (String) ctxt.handleUnexpectedToken(String.class, p);
         }
     
    -    /**
    -     * Helper method that may be used to support fallback for Empty String / Empty Array
    -     * non-standard representations; usually for things serialized as JSON Objects.
    -     *
    -     * @since 2.5
    -     */
    -    @SuppressWarnings("unchecked")
    -    protected T _deserializeFromEmpty(JsonParser p, DeserializationContext ctxt)
    -        throws IOException
    -    {
    -        JsonToken t = p.currentToken();
    -        if (t == JsonToken.START_ARRAY) {
    -            if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
    -                t = p.nextToken();
    -                if (t == JsonToken.END_ARRAY) {
    -                    return null;
    -                }
    -                return (T) ctxt.handleUnexpectedToken(handledType(), p);
    -            }
    -        } else if (t == JsonToken.VALUE_STRING) {
    -            if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
    -                String str = p.getText().trim();
    -                if (str.isEmpty()) {
    -                    return null;
    -                }
    -            }
    -        }
    -        return (T) ctxt.handleUnexpectedToken(handledType(), p);
    -    }
    -
         /**
          * Helper method called to determine if we are seeing String value of
          * "null", and, further, that it should be coerced to null just like
    @@ -674,78 +739,6 @@ protected final boolean _isPosInf(String text) {
     
         protected final boolean _isNaN(String text) { return "NaN".equals(text); }
     
    -    /*
    -    /**********************************************************
    -    /* Helper methods for sub-classes regarding decoding from
    -    /* alternate representations
    -    /**********************************************************
    -     */
    -
    -    /**
    -     * Helper method that allows easy support for array-related {@link DeserializationFeature}s
    -     * `ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT` and `UNWRAP_SINGLE_VALUE_ARRAYS`: checks for either
    -     * empty array, or single-value array-wrapped value (respectively), and either reports
    -     * an exception (if no match, or feature(s) not enabled), or returns appropriate
    -     * result value.
    -     *<p>
    -     * This method should NOT be called if Array representation is explicitly supported
    -     * for type: it should only be called in case it is otherwise unrecognized.
    -     *<p>
    -     * NOTE: in case of unwrapped single element, will handle actual decoding
    -     * by calling {@link #_deserializeWrappedValue}, which by default calls
    -     * {@link #deserialize(JsonParser, DeserializationContext)}.
    -     *
    -     * @since 2.9
    -     */
    -    protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
    -    {
    -        JsonToken t;
    -        if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) {
    -            t = p.nextToken();
    -            if (t == JsonToken.END_ARRAY) {
    -                if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
    -                    return getNullValue(ctxt);
    -                }
    -            }
    -            if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
    -                final T parsed = deserialize(p, ctxt);
    -                if (p.nextToken() != JsonToken.END_ARRAY) {
    -                    handleMissingEndArrayForSingle(p, ctxt);
    -                }
    -                return parsed;
    -            }
    -        } else {
    -            t = p.currentToken();
    -        }
    -        @SuppressWarnings("unchecked")
    -        T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, null);
    -        return result;
    -    }
    -
    -    /**
    -     * Helper called to support {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}:
    -     * default implementation simply calls
    -     * {@link #deserialize(JsonParser, DeserializationContext)},
    -     * but handling may be overridden.
    -     *
    -     * @since 2.9
    -     */
    -    protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt) throws IOException
    -    {
    -        // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
    -        //   either supporting nested arrays, or to cause infinite looping.
    -        if (p.hasToken(JsonToken.START_ARRAY)) {
    -            String msg = String.format(
    -"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
    -                    ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
    -                    "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
    -            @SuppressWarnings("unchecked")
    -            T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
    -            return result;
    -        }
    -        return (T) deserialize(p, ctxt);
    -    }
    -
         /*
         /****************************************************
         /* Helper methods for sub-classes, coercions
    
d499f2e7bbc5

Merge pull request #384 from yinzara/master

https://github.com/FasterXML/jackson-databindTatu SalorantaJan 10, 2014via ghsa
15 files changed · +947 23
  • src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java+8 0 modified
    @@ -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());
         }
    
  • src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java+9 0 modified
    @@ -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.
    +     * <p>
    +     * Feature is disabled by default
    +     */
    +    UNWRAP_SINGLE_VALUE_ARRAYS(false),
     
         /**
          * Feature to allow "unwrapping" root-level JSON value, to match setting of
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java+26 11 modified
    @@ -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);
             }
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java+13 0 modified
    @@ -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());
         }
     
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java+11 0 modified
    @@ -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
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java+48 4 modified
    @@ -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);
             }
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java+11 0 modified
    @@ -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
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java+153 0 modified
    @@ -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;
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java+11 0 modified
    @@ -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<String>
         @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) {
    
  • src/test/java/com/fasterxml/jackson/databind/deser/TestEnumDeserialization.java+20 0 modified
    @@ -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
    +        }
    +    }
     }
    
  • src/test/java/com/fasterxml/jackson/databind/deser/TestExceptionDeserialization.java+41 0 modified
    @@ -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
    +        }
    +    }
     }
    
  • src/test/java/com/fasterxml/jackson/databind/deser/TestGenerics.java+59 0 modified
    @@ -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<SimpleBean> result = mapper.readValue
    +            ("[{\"value\": [{ \"x\" : 13 }] }]",
    +             new TypeReference<Wrapper<SimpleBean>>() { });
    +        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<Wrapper<Long>>() { });
             assertEquals(new Wrapper<Long>(7L), result3);
         }
    +    
    +    //[Issue#381]
    +    public void testMultipleWrappersSingleValueArray() throws Exception
    +    {
    +        ObjectMapper mapper = new ObjectMapper();
    +        mapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
    +
    +        // First, numeric wrapper
    +        Wrapper<Boolean> result = mapper.readValue
    +            ("[{\"value\": [true]}]", new TypeReference<Wrapper<Boolean>>() { });
    +        assertEquals(new Wrapper<Boolean>(Boolean.TRUE), result);
    +
    +        // Then string one
    +        Wrapper<String> result2 = mapper.readValue
    +            ("[{\"value\": [\"abc\"]}]", new TypeReference<Wrapper<String>>() { });
    +        assertEquals(new Wrapper<String>("abc"), result2);
    +
    +        // And then number
    +        Wrapper<Long> result3 = mapper.readValue
    +            ("[{\"value\": [7]}]", new TypeReference<Wrapper<Long>>() { });
    +        assertEquals(new Wrapper<Long>(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<SimpleBean>[] result = mapper.readValue
    +            ("[ {\"value\": [ { \"x\" : [ 9 ] } ] } ]",
    +             new TypeReference<Wrapper<SimpleBean>[]>() { });
    +        assertNotNull(result);
    +        assertEquals(Wrapper[].class, result.getClass());
    +        assertEquals(1, result.length);
    +        Wrapper<SimpleBean> elem = result[0];
    +        Object contents = elem.value;
    +        assertNotNull(contents);
    +        assertEquals(SimpleBean.class, contents.getClass());
    +        SimpleBean bean = (SimpleBean) contents;
    +        assertEquals(9, bean.x);
    +    }
     }
    
  • src/test/java/com/fasterxml/jackson/databind/deser/TestJdkTypes.java+281 0 modified
    @@ -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
    +        }
    +    }
     }
    
  • src/test/java/com/fasterxml/jackson/databind/deser/TestSimpleTypes.java+239 8 modified
    @@ -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));
         }
     
         /*
    
  • src/test/java/com/fasterxml/jackson/databind/deser/TestTimestampDeserialization.java+17 0 modified
    @@ -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());
    +    }
     
         /*
         /**********************************************************
    
0e37a3950243

Adding "UNWRAP_SINGLE_VALUE_ARRAYS" DeserializationFeature

https://github.com/FasterXML/jackson-databindMatthew MorrissetteJan 8, 2014via ghsa
9 files changed · +561 15
  • src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java+9 0 modified
    @@ -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.
    +     * <p>
    +     * Feature is disabled by default
    +     */
    +    UNWRAP_SINGLE_VALUE_ARRAYS(false),
     
         /**
          * Feature to allow "unwrapping" root-level JSON value, to match setting of
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java+26 11 modified
    @@ -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);
             }
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java+13 0 modified
    @@ -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());
         }
     
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java+11 0 modified
    @@ -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
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java+48 4 modified
    @@ -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);
             }
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StackTraceElementDeserializer.java+11 0 modified
    @@ -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
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java+153 0 modified
    @@ -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;
    
  • src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java+11 0 modified
    @@ -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<String>
         @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) {
    
  • src/test/java/com/fasterxml/jackson/databind/deser/TestJdkTypes.java+279 0 modified
    @@ -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
    +        }
    +    }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

19

News mentions

0

No linked articles in our index yet.