Skip to content

Commit 60ae600

Browse files
committed
Fix #994
1 parent 1b7f096 commit 60ae600

File tree

7 files changed

+136
-170
lines changed

7 files changed

+136
-170
lines changed

release-notes/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Project: jackson-databind
2222
#865: `JsonFormat.Shape.OBJECT` ignored when class implements `Map.Entry`
2323
#888: Allow specifying custom exclusion comparator via `@JsonInclude`,
2424
using `JsonInclude.Include.CUSTOM`
25+
#994: `DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS` only works for POJOs, Maps
2526
#1029: Add a way to define property name aliases
2627
#1035: `@JsonAnySetter` assumes key of `String`, does not consider declared type.
2728
(reported by Michael F)

src/main/java/com/fasterxml/jackson/databind/deser/std/DateDeserializers.java

+4-17
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
import java.util.*;
88

99
import com.fasterxml.jackson.annotation.JsonFormat;
10+
1011
import com.fasterxml.jackson.core.JsonParser;
1112
import com.fasterxml.jackson.core.JsonToken;
12-
import com.fasterxml.jackson.databind.BeanProperty;
13-
import com.fasterxml.jackson.databind.DeserializationContext;
14-
import com.fasterxml.jackson.databind.DeserializationFeature;
15-
import com.fasterxml.jackson.databind.JsonDeserializer;
16-
import com.fasterxml.jackson.databind.JsonMappingException;
13+
14+
import com.fasterxml.jackson.databind.*;
1715
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
1816
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
1917
import com.fasterxml.jackson.databind.util.ClassUtil;
@@ -179,8 +177,7 @@ protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
179177
throws IOException
180178
{
181179
if (_customFormat != null) {
182-
JsonToken t = p.getCurrentToken();
183-
if (t == JsonToken.VALUE_STRING) {
180+
if (p.hasToken(JsonToken.VALUE_STRING)) {
184181
String str = p.getText().trim();
185182
if (str.length() == 0) {
186183
return (Date) getEmptyValue(ctxt);
@@ -194,16 +191,6 @@ protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
194191
}
195192
}
196193
}
197-
// [databind#381]
198-
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
199-
p.nextToken();
200-
final Date parsed = _parseDate(p, ctxt);
201-
t = p.nextToken();
202-
if (t != JsonToken.END_ARRAY) {
203-
handleMissingEndArrayForSingle(p, ctxt);
204-
}
205-
return parsed;
206-
}
207194
}
208195
return super._parseDate(p, ctxt);
209196
}

src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java

+12-61
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,8 @@ protected final Boolean _parseBoolean(JsonParser p, DeserializationContext ctxt)
239239
"only \"true\" or \"false\" recognized");
240240
}
241241
// [databind#381]
242-
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
243-
p.nextToken();
244-
final Boolean parsed = _parseBoolean(p, ctxt);
245-
t = p.nextToken();
246-
if (t != JsonToken.END_ARRAY) {
247-
handleMissingEndArrayForSingle(p, ctxt);
248-
}
249-
return parsed;
242+
if (t == JsonToken.START_ARRAY) {
243+
return _deserializeFromArray(p, ctxt);
250244
}
251245
// Otherwise, no can do:
252246
return (Boolean) ctxt.handleUnexpectedToken(_valueClass, p);
@@ -314,14 +308,8 @@ protected Byte _parseByte(JsonParser p, DeserializationContext ctxt) throws IOEx
314308
return (Byte) _coerceNullToken(ctxt, _primitive);
315309
}
316310
// [databind#381]
317-
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
318-
p.nextToken();
319-
final Byte parsed = _parseByte(p, ctxt);
320-
t = p.nextToken();
321-
if (t != JsonToken.END_ARRAY) {
322-
handleMissingEndArrayForSingle(p, ctxt);
323-
}
324-
return parsed;
311+
if (t == JsonToken.START_ARRAY) {
312+
return _deserializeFromArray(p, ctxt);
325313
}
326314
return (Byte) ctxt.handleUnexpectedToken(_valueClass, p);
327315
}
@@ -386,15 +374,8 @@ protected Short _parseShort(JsonParser p, DeserializationContext ctxt) throws IO
386374
if (t == JsonToken.VALUE_NULL) {
387375
return (Short) _coerceNullToken(ctxt, _primitive);
388376
}
389-
// [databind#381]
390-
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
391-
p.nextToken();
392-
final Short parsed = _parseShort(p, ctxt);
393-
t = p.nextToken();
394-
if (t != JsonToken.END_ARRAY) {
395-
handleMissingEndArrayForSingle(p, ctxt);
396-
}
397-
return parsed;
377+
if (t == JsonToken.START_ARRAY) {
378+
return _deserializeFromArray(p, ctxt);
398379
}
399380
return (Short) ctxt.handleUnexpectedToken(_valueClass, p);
400381
}
@@ -521,15 +502,7 @@ protected final Integer _parseInteger(JsonParser p, DeserializationContext ctxt)
521502
case JsonTokenId.ID_NULL:
522503
return (Integer) _coerceNullToken(ctxt, _primitive);
523504
case JsonTokenId.ID_START_ARRAY:
524-
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
525-
p.nextToken();
526-
final Integer parsed = _parseInteger(p, ctxt);
527-
if (p.nextToken() != JsonToken.END_ARRAY) {
528-
handleMissingEndArrayForSingle(p, ctxt);
529-
}
530-
return parsed;
531-
}
532-
break;
505+
return _deserializeFromArray(p, ctxt);
533506
}
534507
// Otherwise, no can do:
535508
return (Integer) ctxt.handleUnexpectedToken(_valueClass, p);
@@ -591,16 +564,7 @@ protected final Long _parseLong(JsonParser p, DeserializationContext ctxt) throw
591564
case JsonTokenId.ID_NULL:
592565
return (Long) _coerceNullToken(ctxt, _primitive);
593566
case JsonTokenId.ID_START_ARRAY:
594-
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
595-
p.nextToken();
596-
final Long parsed = _parseLong(p, ctxt);
597-
JsonToken t = p.nextToken();
598-
if (t != JsonToken.END_ARRAY) {
599-
handleMissingEndArrayForSingle(p, ctxt);
600-
}
601-
return parsed;
602-
}
603-
break;
567+
return _deserializeFromArray(p, ctxt);
604568
}
605569
// Otherwise, no can do:
606570
return (Long) ctxt.handleUnexpectedToken(_valueClass, p);
@@ -670,14 +634,8 @@ protected final Float _parseFloat(JsonParser p, DeserializationContext ctxt)
670634
if (t == JsonToken.VALUE_NULL) {
671635
return (Float) _coerceNullToken(ctxt, _primitive);
672636
}
673-
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
674-
p.nextToken();
675-
final Float parsed = _parseFloat(p, ctxt);
676-
t = p.nextToken();
677-
if (t != JsonToken.END_ARRAY) {
678-
handleMissingEndArrayForSingle(p, ctxt);
679-
}
680-
return parsed;
637+
if (t == JsonToken.START_ARRAY) {
638+
return _deserializeFromArray(p, ctxt);
681639
}
682640
// Otherwise, no can do:
683641
return (Float) ctxt.handleUnexpectedToken(_valueClass, p);
@@ -714,7 +672,6 @@ public Double deserializeWithType(JsonParser p, DeserializationContext ctxt,
714672
protected final Double _parseDouble(JsonParser p, DeserializationContext ctxt) throws IOException
715673
{
716674
JsonToken t = p.getCurrentToken();
717-
718675
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
719676
return p.getDoubleValue();
720677
}
@@ -752,14 +709,8 @@ protected final Double _parseDouble(JsonParser p, DeserializationContext ctxt) t
752709
if (t == JsonToken.VALUE_NULL) {
753710
return (Double) _coerceNullToken(ctxt, _primitive);
754711
}
755-
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
756-
p.nextToken();
757-
final Double parsed = _parseDouble(p, ctxt);
758-
t = p.nextToken();
759-
if (t != JsonToken.END_ARRAY) {
760-
handleMissingEndArrayForSingle(p, ctxt);
761-
}
762-
return parsed;
712+
if (t == JsonToken.START_ARRAY) {
713+
return _deserializeFromArray(p, ctxt);
763714
}
764715
// Otherwise, no can do:
765716
return (Double) ctxt.handleUnexpectedToken(_valueClass, p);

src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java

+109-15
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ public abstract class StdDeserializer<T>
4343
protected final static int F_MASK_INT_COERCIONS =
4444
DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.getMask()
4545
| DeserializationFeature.USE_LONG_FOR_INTS.getMask();
46+
47+
// @since 2.9
48+
protected final static int F_MASK_ACCEPT_ARRAYS =
49+
DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS.getMask() |
50+
DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT.getMask();
51+
4652

4753
/**
4854
* Type of values this deserializer handles: sometimes
@@ -128,7 +134,7 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
128134
TypeDeserializer typeDeserializer) throws IOException {
129135
return typeDeserializer.deserializeTypedFromAny(p, ctxt);
130136
}
131-
137+
132138
/*
133139
/**********************************************************
134140
/* Helper methods for sub-classes, parsing: while mostly
@@ -455,26 +461,42 @@ protected final double _parseDoublePrimitive(DeserializationContext ctxt, String
455461
protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
456462
throws IOException
457463
{
458-
JsonToken t = p.getCurrentToken();
459-
if (t == JsonToken.VALUE_NUMBER_INT) {
464+
switch (p.getCurrentTokenId()) {
465+
case JsonTokenId.ID_STRING:
466+
return _parseDate(p.getText().trim(), ctxt);
467+
case JsonTokenId.ID_NUMBER_INT:
460468
return new java.util.Date(p.getLongValue());
461-
}
462-
if (t == JsonToken.VALUE_NULL) {
469+
case JsonTokenId.ID_NULL:
463470
return (java.util.Date) getNullValue(ctxt);
464-
}
465-
if (t == JsonToken.VALUE_STRING) {
466-
return _parseDate(p.getText().trim(), ctxt);
467-
}
468-
// [databind#381]
469-
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
470-
p.nextToken();
471-
final Date parsed = _parseDate(p, ctxt);
472-
_verifyEndArrayForSingle(p, ctxt);
473-
return parsed;
471+
case JsonTokenId.ID_START_ARRAY:
472+
return _parseDateFromArray(p, ctxt);
474473
}
475474
return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, p);
476475
}
477476

477+
// @since 2.9
478+
protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContext ctxt)
479+
throws IOException
480+
{
481+
JsonToken t;
482+
if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) {
483+
t = p.nextToken();
484+
if (t == JsonToken.END_ARRAY) {
485+
if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
486+
return (java.util.Date) getNullValue(ctxt);
487+
}
488+
}
489+
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
490+
final Date parsed = _parseDate(p, ctxt);
491+
_verifyEndArrayForSingle(p, ctxt);
492+
return parsed;
493+
}
494+
} else {
495+
t = p.getCurrentToken();
496+
}
497+
return (java.util.Date) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
498+
}
499+
478500
/**
479501
* @since 2.8
480502
*/
@@ -594,6 +616,78 @@ protected final boolean _isPosInf(String text) {
594616

595617
protected final boolean _isNaN(String text) { return "NaN".equals(text); }
596618

619+
/*
620+
/**********************************************************
621+
/* Helper methods for sub-classes regarding decoding from
622+
/* alternate representations
623+
/**********************************************************
624+
*/
625+
626+
/**
627+
* Helper method that allows easy support for array-related {@link DeserializationFeature}s
628+
* `ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT` and `UNWRAP_SINGLE_VALUE_ARRAYS`: checks for either
629+
* empty array, or single-value array-wrapped value (respectively), and either reports
630+
* an exception (if no match, or feature(s) not enabled), or returns appropriate
631+
* result value.
632+
*<p>
633+
* This method should NOT be called if Array representation is explicitly supported
634+
* for type: it should only be called in case it is otherwise unrecognized.
635+
*<p>
636+
* NOTE: in case of unwrapped single element, will handle actual decoding
637+
* by calling {@link #_deserializeWrappedValue}, which by default calls
638+
* {@link #deserialize(JsonParser, DeserializationContext)}.
639+
*
640+
* @since 2.9
641+
*/
642+
protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
643+
{
644+
JsonToken t;
645+
if (ctxt.hasSomeOfFeatures(F_MASK_ACCEPT_ARRAYS)) {
646+
t = p.nextToken();
647+
if (t == JsonToken.END_ARRAY) {
648+
if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
649+
return getNullValue(ctxt);
650+
}
651+
}
652+
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
653+
final T parsed = deserialize(p, ctxt);
654+
if (p.nextToken() != JsonToken.END_ARRAY) {
655+
handleMissingEndArrayForSingle(p, ctxt);
656+
}
657+
return parsed;
658+
}
659+
} else {
660+
t = p.getCurrentToken();
661+
}
662+
@SuppressWarnings("unchecked")
663+
T result = (T) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
664+
return result;
665+
}
666+
667+
/**
668+
* Helper called to support {@link DeserializationFeature#UNWRAP_SINGLE_VALUE_ARRAYS}:
669+
* default implementation simply calls
670+
* {@link #deserialize(JsonParser, DeserializationContext)},
671+
* but handling may be overridden.
672+
*
673+
* @since 2.9
674+
*/
675+
protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt) throws IOException
676+
{
677+
// 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
678+
// either supporting nested arrays, or to cause infinite looping.
679+
if (p.hasToken(JsonToken.START_ARRAY)) {
680+
String msg = String.format(
681+
"Can not deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
682+
ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
683+
"DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
684+
@SuppressWarnings("unchecked")
685+
T result = (T) ctxt.handleUnexpectedToken(_valueClass, p.getCurrentToken(), p, msg);
686+
return result;
687+
}
688+
return (T) deserialize(p, ctxt);
689+
}
690+
597691
/*
598692
/****************************************************
599693
/* Helper methods for sub-classes, coercions

src/main/java/com/fasterxml/jackson/databind/deser/std/StdScalarDeserializer.java

+1-34
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
import java.io.IOException;
44

55
import com.fasterxml.jackson.core.*;
6-
import com.fasterxml.jackson.databind.DeserializationConfig;
7-
import com.fasterxml.jackson.databind.DeserializationContext;
8-
import com.fasterxml.jackson.databind.DeserializationFeature;
9-
import com.fasterxml.jackson.databind.JavaType;
6+
import com.fasterxml.jackson.databind.*;
107
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
118
import com.fasterxml.jackson.databind.util.AccessPattern;
129

@@ -16,11 +13,6 @@
1613
*/
1714
public abstract class StdScalarDeserializer<T> extends StdDeserializer<T>
1815
{
19-
// @since 2.8.8
20-
protected final static int FEATURES_ACCEPT_ARRAYS =
21-
DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS.getMask() |
22-
DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT.getMask();
23-
2416
private static final long serialVersionUID = 1L;
2517

2618
protected StdScalarDeserializer(Class<?> vc) { super(vc); }
@@ -67,29 +59,4 @@ public AccessPattern getNullAccessPattern() {
6759
public AccessPattern getEmptyAccessPattern() {
6860
return AccessPattern.CONSTANT;
6961
}
70-
71-
protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
72-
{
73-
JsonToken t;
74-
if (ctxt.hasSomeOfFeatures(FEATURES_ACCEPT_ARRAYS)) {
75-
t = p.nextToken();
76-
if (t == JsonToken.END_ARRAY) {
77-
if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
78-
return getNullValue(ctxt);
79-
}
80-
}
81-
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
82-
final T parsed = deserialize(p, ctxt);
83-
if (p.nextToken() != JsonToken.END_ARRAY) {
84-
handleMissingEndArrayForSingle(p, ctxt);
85-
}
86-
return parsed;
87-
}
88-
} else {
89-
t = p.getCurrentToken();
90-
}
91-
@SuppressWarnings("unchecked")
92-
T result = (T) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
93-
return result;
94-
}
9562
}

0 commit comments

Comments
 (0)