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.
| Package | Affected versions | Patched versions |
|---|---|---|
com.fasterxml.jackson.core:jackson-databindMaven | >= 2.4.0-rc1, < 2.12.7.1 | 2.12.7.1 |
com.fasterxml.jackson.core:jackson-databindMaven | >= 2.13.0, < 2.13.4.2 | 2.13.4.2 |
Affected products
47- FasterXML/jackson-databinddescription
- osv-coords46 versionspkg:apk/chainguard/cassandra-5.0pkg:apk/chainguard/cassandra-5.0-compatpkg:apk/chainguard/cassandra-5.0-entrypoint-compatpkg:apk/chainguard/cassandra-5.0-iamguarded-compatpkg:apk/chainguard/cassandra-fips-5.0pkg:apk/chainguard/cassandra-fips-5.0-compatpkg:apk/chainguard/cassandra-reaperpkg:apk/chainguard/cqlsh-5.0pkg:apk/chainguard/cqlsh-fips-5.0pkg:apk/chainguard/druidpkg:apk/chainguard/druid-compatpkg:apk/chainguard/management-api-for-apache-cassandra-4.0pkg:apk/chainguard/management-api-for-apache-cassandra-4.0-compatpkg:apk/chainguard/management-api-for-apache-cassandra-4.1pkg:apk/chainguard/management-api-for-apache-cassandra-5.0pkg:apk/chainguard/management-api-for-apache-cassandra-5.0-compatpkg:apk/chainguard/request-1277pkg:apk/chainguard/spark-3.5-scala-2.13pkg:apk/wolfi/cassandra-5.0pkg:apk/wolfi/cassandra-5.0-compatpkg:apk/wolfi/cassandra-5.0-entrypoint-compatpkg:apk/wolfi/cassandra-5.0-iamguarded-compatpkg:apk/wolfi/cassandra-reaperpkg:apk/wolfi/cqlsh-5.0pkg:apk/wolfi/druidpkg:apk/wolfi/druid-compatpkg:apk/wolfi/management-api-for-apache-cassandra-4.1pkg:apk/wolfi/management-api-for-apache-cassandra-5.0pkg:apk/wolfi/management-api-for-apache-cassandra-5.0-compatpkg:apk/wolfi/spark-3.5-scala-2.13pkg:maven/com.fasterxml.jackson.core/jackson-databindpkg:rpm/opensuse/jackson-databind&distro=openSUSE%20Leap%2015.3pkg:rpm/opensuse/jackson-databind&distro=openSUSE%20Leap%2015.4pkg:rpm/suse/jackson-databind&distro=SUSE%20Enterprise%20Storage%207pkg:rpm/suse/jackson-databind&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP2-ESPOSpkg:rpm/suse/jackson-databind&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP2-LTSSpkg:rpm/suse/jackson-databind&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Basesystem%2015%20SP3pkg:rpm/suse/jackson-databind&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Basesystem%2015%20SP4pkg:rpm/suse/jackson-databind&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Development%20Tools%2015%20SP3pkg:rpm/suse/jackson-databind&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP2-BCLpkg:rpm/suse/jackson-databind&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP2-LTSSpkg:rpm/suse/jackson-databind&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP2pkg:rpm/suse/jackson-databind&distro=SUSE%20Manager%20Proxy%204.1pkg:rpm/suse/jackson-databind&distro=SUSE%20Manager%20Retail%20Branch%20Server%204.1pkg:rpm/suse/jackson-databind&distro=SUSE%20Manager%20Server%204.1pkg:rpm/suse/jackson-databind&distro=SUSE%20Manager%20Server%20Module%204.3
< 5.0.3-r2+ 45 more
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 4.0.1-r1
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0.1.89-r0
- (no CPE)range: < 0.1.89-r0
- (no CPE)range: < 0.1.109-r0
- (no CPE)range: < 0.1.89-r0
- (no CPE)range: < 0.1.89-r0
- (no CPE)range: < 0.1.89-r0
- (no CPE)range: < 3.5.7-r2
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 4.0.1-r1
- (no CPE)range: < 5.0.3-r2
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0.1.109-r0
- (no CPE)range: < 0.1.89-r0
- (no CPE)range: < 0.1.89-r0
- (no CPE)range: < 3.5.7-r2
- (no CPE)range: >= 2.4.0-rc1, < 2.12.7.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
- (no CPE)range: < 2.13.4.2-150200.3.12.1
Patches
6cd090979b7eabackport Fix #3590 and Fix #3582 (#3622)
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)
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(); + } + +}
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(); + } + +}
7ba9ac5b87a9Yet more refactoring to figure out coercion for empty String
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
d499f2e7bbc5Merge pull request #384 from yinzara/master
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()); + } /* /**********************************************************
0e37a3950243Adding "UNWRAP_SINGLE_VALUE_ARRAYS" DeserializationFeature
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- github.com/advisories/GHSA-jjjh-jjxp-wpffghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-42003ghsaADVISORY
- security.gentoo.org/glsa/202210-21ghsavendor-advisoryWEB
- www.debian.org/security/2022/dsa-5283ghsavendor-advisoryWEB
- bugs.chromium.org/p/oss-fuzz/issues/detailghsaWEB
- github.com/FasterXML/jackson-databind/blob/2.13/release-notes/VERSION-2.xghsaWEB
- github.com/FasterXML/jackson-databind/commit/0e37a39502439ecbaa1a5b5188387c01bf7f7fa1ghsaWEB
- github.com/FasterXML/jackson-databind/commit/2c4a601c626f7790cad9d3c322d244e182838288ghsaWEB
- github.com/FasterXML/jackson-databind/commit/7ba9ac5b87a9d6ac0d2815158ecbeb315ad4dcdcghsaWEB
- github.com/FasterXML/jackson-databind/commit/cd090979b7ea78c75e4de8a4aed04f7e9fa8deeaghsaWEB
- github.com/FasterXML/jackson-databind/commit/d499f2e7bbc5ebd63af11e1f5cf1989fa323aa45ghsaWEB
- github.com/FasterXML/jackson-databind/commit/d78d00ee7b5245b93103fef3187f70543d67ca33ghsaWEB
- github.com/FasterXML/jackson-databind/commits/jackson-databind-2.4.0-rc1ghsaWEB
- github.com/FasterXML/jackson-databind/compare/jackson-databind-2.13.4.1...jackson-databind-2.13.4.2ghsaWEB
- github.com/FasterXML/jackson-databind/issues/3590ghsaWEB
- github.com/FasterXML/jackson-databind/issues/3627ghsaWEB
- lists.debian.org/debian-lts-announce/2022/11/msg00035.htmlghsamailing-listWEB
- security.netapp.com/advisory/ntap-20221124-0004ghsaWEB
- security.netapp.com/advisory/ntap-20221124-0004/mitre
News mentions
0No linked articles in our index yet.