Skip to content

Commit 9f68b51

Browse files
authored
Implement empty string coercion for StringCollectionDeserializer (#3418)
1 parent 851172c commit 9f68b51

File tree

3 files changed

+146
-8
lines changed

3 files changed

+146
-8
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,10 @@ protected Collection<Object> _deserializeFromString(JsonParser p, Deserializatio
305305
if (value.isEmpty()) {
306306
CoercionAction act = ctxt.findCoercionAction(logicalType(), rawTargetType,
307307
CoercionInputShape.EmptyString);
308-
act = _checkCoercionFail(ctxt, act, rawTargetType, value,
309-
"empty String (\"\")");
310-
if (act != null) {
311-
// handleNonArray may successfully deserialize the result (if
312-
// ACCEPT_SINGLE_VALUE_AS_ARRAY is enabled, for example) otherwise it
313-
// is capable of failing just as well as _deserializeFromEmptyString.
308+
// handleNonArray may successfully deserialize the result (if
309+
// ACCEPT_SINGLE_VALUE_AS_ARRAY is enabled, for example) otherwise it
310+
// is capable of failing just as well as _deserializeFromEmptyString.
311+
if (act != null && act != CoercionAction.Fail) {
314312
return (Collection<Object>) _deserializeFromEmptyString(
315313
p, ctxt, act, rawTargetType, "empty String (\"\")");
316314
}
@@ -320,8 +318,10 @@ protected Collection<Object> _deserializeFromString(JsonParser p, Deserializatio
320318
else if (_isBlank(value)) {
321319
final CoercionAction act = ctxt.findCoercionFromBlankString(logicalType(), rawTargetType,
322320
CoercionAction.Fail);
323-
return (Collection<Object>) _deserializeFromEmptyString(
324-
p, ctxt, act, rawTargetType, "blank String (all whitespace)");
321+
if (act != CoercionAction.Fail) {
322+
return (Collection<Object>) _deserializeFromEmptyString(
323+
p, ctxt, act, rawTargetType, "blank String (all whitespace)");
324+
}
325325
}
326326
return handleNonArray(p, ctxt, createDefaultInstance(ctxt));
327327
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import com.fasterxml.jackson.databind.*;
1212
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
13+
import com.fasterxml.jackson.databind.cfg.CoercionAction;
14+
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
1315
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
1416
import com.fasterxml.jackson.databind.deser.NullValueProvider;
1517
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
@@ -298,6 +300,27 @@ private final Collection<String> handleNonArray(JsonParser p, DeserializationCon
298300
}
299301
value = (String) _nullProvider.getNullValue(ctxt);
300302
} else {
303+
if (p.hasToken(JsonToken.VALUE_STRING)) {
304+
String textValue = p.getText();
305+
// https://github.com/FasterXML/jackson-dataformat-xml/issues/513
306+
if (textValue.isEmpty()) {
307+
final CoercionAction act = ctxt.findCoercionAction(logicalType(), handledType(),
308+
CoercionInputShape.EmptyString);
309+
if (act != CoercionAction.Fail) {
310+
return (Collection<String>) _deserializeFromEmptyString(p, ctxt, act, handledType(),
311+
"empty String (\"\")");
312+
}
313+
} else if (_isBlank(textValue)) {
314+
final CoercionAction act = ctxt.findCoercionFromBlankString(logicalType(), handledType(),
315+
CoercionAction.Fail);
316+
if (act != CoercionAction.Fail) {
317+
return (Collection<String>) _deserializeFromEmptyString(p, ctxt, act, handledType(),
318+
"blank String (all whitespace)");
319+
}
320+
}
321+
// if coercion failed, we can still add it to a list
322+
}
323+
301324
try {
302325
value = (valueDes == null) ? _parseString(p, ctxt) : valueDes.deserialize(p, ctxt);
303326
} catch (Exception e) {
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.fasterxml.jackson.databind.deser.std;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.core.JsonProcessingException;
5+
import com.fasterxml.jackson.core.type.TypeReference;
6+
import com.fasterxml.jackson.databind.DeserializationFeature;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.databind.cfg.CoercionAction;
9+
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
10+
import org.junit.Assert;
11+
import org.junit.Test;
12+
13+
import java.util.Collections;
14+
import java.util.List;
15+
16+
public class EmptyStringAsSingleValueTest {
17+
private static ObjectMapper normalMapper() {
18+
ObjectMapper mapper = new ObjectMapper();
19+
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
20+
return mapper;
21+
}
22+
23+
private static ObjectMapper coercionMapper() {
24+
ObjectMapper mapper = normalMapper();
25+
// same as XmlMapper
26+
mapper.coercionConfigDefaults()
27+
.setAcceptBlankAsEmpty(true)
28+
.setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
29+
return mapper;
30+
}
31+
32+
@Test
33+
public void emptyToList() throws JsonProcessingException {
34+
// NO coercion + empty string input + StringCollectionDeserializer
35+
Assert.assertEquals(Collections.singletonList(""), normalMapper().readValue("\"\"", new TypeReference<List<String>>() {}));
36+
}
37+
38+
@Test
39+
public void emptyToListWrapper() throws JsonProcessingException {
40+
// NO coercion + empty string input + normal CollectionDeserializer
41+
Assert.assertEquals(Collections.singletonList(new StringWrapper("")), normalMapper().readValue("\"\"", new TypeReference<List<StringWrapper>>() {}));
42+
}
43+
44+
@Test
45+
public void coercedEmptyToList() throws JsonProcessingException {
46+
// YES coercion + empty string input + StringCollectionDeserializer
47+
Assert.assertEquals(Collections.emptyList(), coercionMapper().readValue("\"\"", new TypeReference<List<String>>() {}));
48+
}
49+
50+
@Test
51+
public void coercedEmptyToListWrapper() throws JsonProcessingException {
52+
// YES coercion + empty string input + normal CollectionDeserializer
53+
Assert.assertEquals(Collections.emptyList(), coercionMapper().readValue("\"\"", new TypeReference<List<StringWrapper>>() {}));
54+
}
55+
56+
@Test
57+
public void coercedListToList() throws JsonProcessingException {
58+
// YES coercion + empty LIST input + StringCollectionDeserializer
59+
Assert.assertEquals(Collections.emptyList(), coercionMapper().readValue("[]", new TypeReference<List<String>>() {}));
60+
}
61+
62+
@Test
63+
public void coercedListToListWrapper() throws JsonProcessingException {
64+
// YES coercion + empty LIST input + normal CollectionDeserializer
65+
Assert.assertEquals(Collections.emptyList(), coercionMapper().readValue("[]", new TypeReference<List<StringWrapper>>() {}));
66+
}
67+
68+
@Test
69+
public void blankToList() throws JsonProcessingException {
70+
// NO coercion + empty string input + StringCollectionDeserializer
71+
Assert.assertEquals(Collections.singletonList(" "), normalMapper().readValue("\" \"", new TypeReference<List<String>>() {}));
72+
}
73+
74+
@Test
75+
public void blankToListWrapper() throws JsonProcessingException {
76+
// NO coercion + empty string input + normal CollectionDeserializer
77+
Assert.assertEquals(Collections.singletonList(new StringWrapper(" ")), normalMapper().readValue("\" \"", new TypeReference<List<StringWrapper>>() {}));
78+
}
79+
80+
@Test
81+
public void coercedBlankToList() throws JsonProcessingException {
82+
// YES coercion + empty string input + StringCollectionDeserializer
83+
Assert.assertEquals(Collections.emptyList(), coercionMapper().readValue("\" \"", new TypeReference<List<String>>() {}));
84+
}
85+
86+
@Test
87+
public void coercedBlankToListWrapper() throws JsonProcessingException {
88+
// YES coercion + empty string input + normal CollectionDeserializer
89+
Assert.assertEquals(Collections.emptyList(), coercionMapper().readValue("\" \"", new TypeReference<List<StringWrapper>>() {}));
90+
}
91+
92+
private static final class StringWrapper {
93+
private final String s;
94+
95+
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
96+
public StringWrapper(String s) {
97+
this.s = s;
98+
}
99+
100+
@Override
101+
public String toString() {
102+
return "StringWrapper{" + s + "}";
103+
}
104+
105+
@Override
106+
public boolean equals(Object obj) {
107+
return obj instanceof StringWrapper && ((StringWrapper) obj).s.equals(s);
108+
}
109+
110+
@Override
111+
public int hashCode() {
112+
return s.hashCode();
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)