Skip to content

Commit 23d49e3

Browse files
committed
Improve handling wrt #994, now supporting more types but not all (full fix for 2.9)
1 parent 8d5bd66 commit 23d49e3

File tree

8 files changed

+224
-99
lines changed

8 files changed

+224
-99
lines changed

release-notes/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Project: jackson-databind
55

66
2.8.8 (not yet released)
77

8+
(partial) #994: `DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS` only works for POJOs, Maps
89
#1533: `AsPropertyTypeDeserializer` ignores `DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT`
910
#1543: JsonFormat.Shape.NUMBER_INT does not work when defined on enum type in 2.8
1011
(reported by Alex P)

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,8 @@ public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOExcepti
139139
}
140140
JsonToken t = p.getCurrentToken();
141141
// [databind#381]
142-
if (t == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
143-
p.nextToken();
144-
final T value = deserialize(p, ctxt);
145-
if (p.nextToken() != JsonToken.END_ARRAY) {
146-
handleMissingEndArrayForSingle(p, ctxt);
147-
}
148-
return value;
142+
if (t == JsonToken.START_ARRAY) {
143+
return _deserializeFromArray(p, ctxt);
149144
}
150145
if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
151146
// Trivial cases; null to null, instance of type itself returned as is

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

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public BooleanDeserializer(Class<Boolean> cls, Boolean nvl)
171171
{
172172
super(cls, nvl);
173173
}
174-
174+
175175
@Override
176176
public Boolean deserialize(JsonParser j, DeserializationContext ctxt) throws IOException
177177
{
@@ -269,14 +269,7 @@ public Character deserialize(JsonParser p, DeserializationContext ctxt)
269269
}
270270
break;
271271
case JsonTokenId.ID_START_ARRAY:
272-
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
273-
p.nextToken();
274-
final Character C = deserialize(p, ctxt);
275-
if (p.nextToken() != JsonToken.END_ARRAY) {
276-
handleMissingEndArrayForSingle(p, ctxt);
277-
}
278-
return C;
279-
}
272+
return _deserializeFromArray(p, ctxt);
280273
default:
281274
}
282275
return (Character) ctxt.handleUnexpectedToken(_valueClass, p);
@@ -474,15 +467,7 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
474467
"not a valid number");
475468
}
476469
case JsonTokenId.ID_START_ARRAY:
477-
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
478-
p.nextToken();
479-
final Object value = deserialize(p, ctxt);
480-
if (p.nextToken() != JsonToken.END_ARRAY) {
481-
handleMissingEndArrayForSingle(p, ctxt);
482-
}
483-
return value;
484-
}
485-
break;
470+
return _deserializeFromArray(p, ctxt);
486471
}
487472
// Otherwise, no can do:
488473
return ctxt.handleUnexpectedToken(_valueClass, p);
@@ -549,15 +534,7 @@ public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws
549534
}
550535
return p.getDecimalValue().toBigInteger();
551536
case JsonTokenId.ID_START_ARRAY:
552-
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
553-
p.nextToken();
554-
final BigInteger value = deserialize(p, ctxt);
555-
if (p.nextToken() != JsonToken.END_ARRAY) {
556-
handleMissingEndArrayForSingle(p, ctxt);
557-
}
558-
return value;
559-
}
560-
break;
537+
return _deserializeFromArray(p, ctxt);
561538
case JsonTokenId.ID_STRING: // let's do implicit re-parse
562539
String text = p.getText().trim();
563540
if (text.length() == 0) {
@@ -604,15 +581,7 @@ public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt)
604581
"not a valid representation");
605582
}
606583
case JsonTokenId.ID_START_ARRAY:
607-
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
608-
p.nextToken();
609-
final BigDecimal value = deserialize(p, ctxt);
610-
if (p.nextToken() != JsonToken.END_ARRAY) {
611-
handleMissingEndArrayForSingle(p, ctxt);
612-
}
613-
return value;
614-
}
615-
break;
584+
return _deserializeFromArray(p, ctxt);
616585
}
617586
// Otherwise, no can do:
618587
return (BigDecimal) ctxt.handleUnexpectedToken(_valueClass, p);

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import com.fasterxml.jackson.core.*;
66
import com.fasterxml.jackson.databind.DeserializationContext;
7+
import com.fasterxml.jackson.databind.DeserializationFeature;
78
import com.fasterxml.jackson.databind.JavaType;
89
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
910

@@ -13,6 +14,11 @@
1314
*/
1415
public abstract class StdScalarDeserializer<T> extends StdDeserializer<T>
1516
{
17+
// @since 2.8.8
18+
protected final static int FEATURES_ACCEPT_ARRAYS =
19+
DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS.getMask() |
20+
DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT.getMask();
21+
1622
private static final long serialVersionUID = 1L;
1723

1824
protected StdScalarDeserializer(Class<?> vc) { super(vc); }
@@ -22,7 +28,32 @@ public abstract class StdScalarDeserializer<T> extends StdDeserializer<T>
2228
protected StdScalarDeserializer(StdScalarDeserializer<?> src) { super(src); }
2329

2430
@Override
25-
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
26-
return typeDeserializer.deserializeTypedFromScalar(jp, ctxt);
31+
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
32+
return typeDeserializer.deserializeTypedFromScalar(p, ctxt);
33+
}
34+
35+
protected T _deserializeFromArray(JsonParser p, DeserializationContext ctxt) throws IOException
36+
{
37+
JsonToken t;
38+
if (ctxt.hasSomeOfFeatures(FEATURES_ACCEPT_ARRAYS)) {
39+
t = p.nextToken();
40+
if (t == JsonToken.END_ARRAY) {
41+
if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)) {
42+
return getNullValue(ctxt);
43+
}
44+
}
45+
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
46+
final T parsed = deserialize(p, ctxt);
47+
if (p.nextToken() != JsonToken.END_ARRAY) {
48+
handleMissingEndArrayForSingle(p, ctxt);
49+
}
50+
return parsed;
51+
}
52+
} else {
53+
t = p.getCurrentToken();
54+
}
55+
@SuppressWarnings("unchecked")
56+
T result = (T) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
57+
return result;
2758
}
2859
}

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ public final class StringDeserializer extends StdScalarDeserializer<String>
1313
{
1414
private static final long serialVersionUID = 1L;
1515

16+
// @since 2.8.8
17+
protected final static int FEATURES_ACCEPT_ARRAYS =
18+
DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS.getMask() |
19+
DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT.getMask();
20+
1621
/**
1722
* @since 2.2
1823
*/
@@ -32,13 +37,8 @@ public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
3237
}
3338
JsonToken t = p.getCurrentToken();
3439
// [databind#381]
35-
if ((t == JsonToken.START_ARRAY) && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
36-
p.nextToken();
37-
final String parsed = _parseString(p, ctxt);
38-
if (p.nextToken() != JsonToken.END_ARRAY) {
39-
handleMissingEndArrayForSingle(p, ctxt);
40-
}
41-
return parsed;
40+
if (t == JsonToken.START_ARRAY) {
41+
return _deserializeFromArray(p, ctxt);
4242
}
4343
// need to gracefully handle byte[] data, as base64
4444
if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
@@ -66,4 +66,28 @@ public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
6666
public String deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
6767
return deserialize(p, ctxt);
6868
}
69+
70+
// @since 2.8.8
71+
protected String _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 String parsed = _parseString(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+
return (String) ctxt.handleUnexpectedToken(_valueClass, t, p, null);
92+
}
6993
}

src/test/java/com/fasterxml/jackson/databind/deser/TestBeanDeserializer.java

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -292,28 +292,6 @@ public void testPOJOFromEmptyString() throws Exception
292292
assertNull(result);
293293
}
294294

295-
// [Databind#540]
296-
public void testPOJOFromEmptyArray() throws Exception
297-
{
298-
final String JSON = " [\n]";
299-
assertFalse(MAPPER.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
300-
// first, verify default settings which do not accept empty Array
301-
ObjectMapper mapper = new ObjectMapper();
302-
try {
303-
mapper.readValue(JSON, Bean.class);
304-
fail("Should not accept Empty Array for POJO by default");
305-
} catch (JsonProcessingException e) {
306-
verifyException(e, "START_ARRAY token");
307-
assertValidLocation(e.getLocation());
308-
}
309-
310-
// should be ok to enable dynamically:
311-
ObjectReader r = MAPPER.readerFor(Bean.class)
312-
.with(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);
313-
Bean result = r.readValue(JSON);
314-
assertNull(result);
315-
}
316-
317295
// [databind#120]
318296
public void testModifyArrayDeserializer() throws Exception
319297
{

src/test/java/com/fasterxml/jackson/databind/deser/TestMapDeserialization.java

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -298,31 +298,6 @@ public void testGenericStringIntMap() throws Exception
298298
assertNull(result.get(""));
299299
}
300300

301-
// [Databind#540]
302-
public void testMapFromEmptyArray() throws Exception
303-
{
304-
final String JSON = " [\n]";
305-
assertFalse(MAPPER.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT));
306-
// first, verify default settings which do not accept empty Array
307-
ObjectMapper mapper = new ObjectMapper();
308-
try {
309-
mapper.readValue(JSON, Map.class);
310-
fail("Should not accept Empty Array for Map by default");
311-
} catch (JsonProcessingException e) {
312-
verifyException(e, "START_ARRAY token");
313-
}
314-
// should be ok to enable dynamically:
315-
ObjectReader r = MAPPER.readerFor(Map.class)
316-
.with(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);
317-
318-
Map<?,?> result = r.readValue(JSON);
319-
assertNull(result);
320-
321-
EnumMap<?,?> result2 = r.forType(new TypeReference<EnumMap<Key,String>>() { })
322-
.readValue(JSON);
323-
assertNull(result2);
324-
}
325-
326301
/*
327302
/**********************************************************
328303
/* Test methods, maps with enums

0 commit comments

Comments
 (0)