• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.convert;
2 
3 import java.io.IOException;
4 import java.util.concurrent.atomic.AtomicBoolean;
5 
6 import com.fasterxml.jackson.core.JsonParser;
7 import com.fasterxml.jackson.core.JsonToken;
8 
9 import com.fasterxml.jackson.databind.BaseMapTest;
10 import com.fasterxml.jackson.databind.MapperFeature;
11 import com.fasterxml.jackson.databind.ObjectMapper;
12 import com.fasterxml.jackson.databind.ObjectReader;
13 import com.fasterxml.jackson.databind.cfg.CoercionAction;
14 import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
15 import com.fasterxml.jackson.databind.exc.MismatchedInputException;
16 import com.fasterxml.jackson.databind.type.LogicalType;
17 
18 public class CoerceToBooleanTest extends BaseMapTest
19 {
20     static class BooleanPOJO {
21         public boolean value;
22     }
23 
24     private final ObjectMapper DEFAULT_MAPPER = sharedMapper();
25 
26     private final ObjectMapper LEGACY_NONCOERCING_MAPPER = jsonMapperBuilder()
27             .disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
28             .build();
29 
30     private final ObjectMapper MAPPER_TO_EMPTY; {
31         MAPPER_TO_EMPTY = newJsonMapper();
32         MAPPER_TO_EMPTY.coercionConfigFor(LogicalType.Boolean)
33             .setCoercion(CoercionInputShape.Integer, CoercionAction.AsEmpty);
34     }
35 
36     private final ObjectMapper MAPPER_TRY_CONVERT; {
37         MAPPER_TRY_CONVERT = newJsonMapper();
38         MAPPER_TRY_CONVERT.coercionConfigFor(LogicalType.Boolean)
39             .setCoercion(CoercionInputShape.Integer, CoercionAction.TryConvert);
40     }
41 
42     private final ObjectMapper MAPPER_TO_NULL; {
43         MAPPER_TO_NULL = newJsonMapper();
44         MAPPER_TO_NULL.coercionConfigFor(LogicalType.Boolean)
45             .setCoercion(CoercionInputShape.Integer, CoercionAction.AsNull);
46     }
47 
48     private final ObjectMapper MAPPER_TO_FAIL; {
49         MAPPER_TO_FAIL = newJsonMapper();
50         MAPPER_TO_FAIL.coercionConfigFor(LogicalType.Boolean)
51             .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail);
52     }
53 
54     private final static String DOC_WITH_0 = aposToQuotes("{'value':0}");
55     private final static String DOC_WITH_1 = aposToQuotes("{'value':1}");
56 
57     /*
58     /**********************************************************
59     /* Unit tests: default, legacy configuration
60     /**********************************************************
61      */
62 
testToBooleanCoercionSuccessPojo()63     public void testToBooleanCoercionSuccessPojo() throws Exception
64     {
65         BooleanPOJO p;
66         final ObjectReader r = DEFAULT_MAPPER.readerFor(BooleanPOJO.class);
67 
68         p = r.readValue(DOC_WITH_0);
69         assertEquals(false, p.value);
70         p = r.readValue(utf8Bytes(DOC_WITH_0));
71         assertEquals(false, p.value);
72 
73         p = r.readValue(DOC_WITH_1);
74         assertEquals(true, p.value);
75         p = r.readValue(utf8Bytes(DOC_WITH_1));
76         assertEquals(true, p.value);
77     }
78 
testToBooleanCoercionSuccessRoot()79     public void testToBooleanCoercionSuccessRoot() throws Exception
80     {
81         final ObjectReader br = DEFAULT_MAPPER.readerFor(Boolean.class);
82 
83         assertEquals(Boolean.FALSE, br.readValue(" 0"));
84         assertEquals(Boolean.FALSE, br.readValue(utf8Bytes(" 0")));
85         assertEquals(Boolean.TRUE, br.readValue(" -1"));
86         assertEquals(Boolean.TRUE, br.readValue(utf8Bytes(" -1")));
87 
88         final ObjectReader atomicR = DEFAULT_MAPPER.readerFor(AtomicBoolean.class);
89 
90         AtomicBoolean ab;
91 
92         ab = atomicR.readValue(" 0");
93         ab = atomicR.readValue(utf8Bytes(" 0"));
94         assertEquals(false, ab.get());
95 
96         ab = atomicR.readValue(" 111");
97         assertEquals(true, ab.get());
98         ab = atomicR.readValue(utf8Bytes(" 111"));
99         assertEquals(true, ab.get());
100     }
101 
testToBooleanCoercionFailBytes()102     public void testToBooleanCoercionFailBytes() throws Exception
103     {
104         _verifyBooleanCoerceFail(aposToQuotes("{'value':1}"), true, JsonToken.VALUE_NUMBER_INT, "1", BooleanPOJO.class);
105 
106         _verifyBooleanCoerceFail("1", true, JsonToken.VALUE_NUMBER_INT, "1", Boolean.TYPE);
107         _verifyBooleanCoerceFail("1", true, JsonToken.VALUE_NUMBER_INT, "1", Boolean.class);
108     }
109 
testToBooleanCoercionFailChars()110     public void testToBooleanCoercionFailChars() throws Exception
111     {
112         _verifyBooleanCoerceFail(aposToQuotes("{'value':1}"), false, JsonToken.VALUE_NUMBER_INT, "1", BooleanPOJO.class);
113 
114         _verifyBooleanCoerceFail("1", false, JsonToken.VALUE_NUMBER_INT, "1", Boolean.TYPE);
115         _verifyBooleanCoerceFail("1", false, JsonToken.VALUE_NUMBER_INT, "1", Boolean.class);
116     }
117 
118     /*
119     /**********************************************************
120     /* Unit tests: new CoercionConfig, as-null, as-empty, try-coerce
121     /**********************************************************
122      */
123 
testIntToNullCoercion()124     public void testIntToNullCoercion() throws Exception
125     {
126         assertNull(MAPPER_TO_NULL.readValue("0", Boolean.class));
127         assertNull(MAPPER_TO_NULL.readValue("1", Boolean.class));
128 
129         // but due to coercion to `boolean`, can not return null here -- however,
130         // goes "1 -> false (no null for primitive) -> Boolean.FALSE
131         assertEquals(Boolean.FALSE, MAPPER_TO_NULL.readValue("0", Boolean.TYPE));
132         assertEquals(Boolean.FALSE, MAPPER_TO_NULL.readValue("1", Boolean.TYPE));
133 
134         // As to AtomicBoolean: that type itself IS nullable since it's of LogicalType.Boolean so
135         assertNull(MAPPER_TO_NULL.readValue("0", AtomicBoolean.class));
136         assertNull(MAPPER_TO_NULL.readValue("1", AtomicBoolean.class));
137 
138         BooleanPOJO p;
139         p = MAPPER_TO_NULL.readValue(DOC_WITH_0, BooleanPOJO.class);
140         assertFalse(p.value);
141         p = MAPPER_TO_NULL.readValue(DOC_WITH_1, BooleanPOJO.class);
142         assertFalse(p.value);
143     }
144 
testIntToEmptyCoercion()145     public void testIntToEmptyCoercion() throws Exception
146     {
147         // "empty" value for Boolean/boolean is False/false
148 
149         assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("0", Boolean.class));
150         assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("1", Boolean.class));
151 
152         assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("0", Boolean.TYPE));
153         assertEquals(Boolean.FALSE, MAPPER_TO_EMPTY.readValue("1", Boolean.TYPE));
154 
155         AtomicBoolean ab;
156         ab = MAPPER_TO_EMPTY.readValue("0", AtomicBoolean.class);
157         assertFalse(ab.get());
158         ab = MAPPER_TO_EMPTY.readValue("1", AtomicBoolean.class);
159         assertFalse(ab.get());
160 
161         BooleanPOJO p;
162         p = MAPPER_TO_EMPTY.readValue(DOC_WITH_0, BooleanPOJO.class);
163         assertFalse(p.value);
164         p = MAPPER_TO_EMPTY.readValue(DOC_WITH_1, BooleanPOJO.class);
165         assertFalse(p.value);
166     }
167 
testIntToTryCoercion()168     public void testIntToTryCoercion() throws Exception
169     {
170         // And "TryCoerce" should do what would be typically expected
171 
172         assertEquals(Boolean.FALSE, MAPPER_TRY_CONVERT.readValue("0", Boolean.class));
173         assertEquals(Boolean.TRUE, MAPPER_TRY_CONVERT.readValue("1", Boolean.class));
174 
175         assertEquals(Boolean.FALSE, MAPPER_TRY_CONVERT.readValue("0", Boolean.TYPE));
176         assertEquals(Boolean.TRUE, MAPPER_TRY_CONVERT.readValue("1", Boolean.TYPE));
177 
178         AtomicBoolean ab;
179         ab = MAPPER_TRY_CONVERT.readValue("0", AtomicBoolean.class);
180         assertFalse(ab.get());
181         ab = MAPPER_TRY_CONVERT.readValue("1", AtomicBoolean.class);
182         assertTrue(ab.get());
183 
184         BooleanPOJO p;
185         p = MAPPER_TRY_CONVERT.readValue(DOC_WITH_0, BooleanPOJO.class);
186         assertFalse(p.value);
187         p = MAPPER_TRY_CONVERT.readValue(DOC_WITH_1, BooleanPOJO.class);
188         assertTrue(p.value);
189     }
190 
191     /*
192     /**********************************************************
193     /* Unit tests: new CoercionConfig, failing
194     /**********************************************************
195      */
196 
testFailFromInteger()197     public void testFailFromInteger() throws Exception
198     {
199         _verifyFailFromInteger(MAPPER_TO_FAIL, BooleanPOJO.class, DOC_WITH_0, Boolean.TYPE);
200         _verifyFailFromInteger(MAPPER_TO_FAIL, BooleanPOJO.class, DOC_WITH_1, Boolean.TYPE);
201 
202         _verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.class, "0");
203         _verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.class, "42");
204 
205         _verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.TYPE, "0");
206         _verifyFailFromInteger(MAPPER_TO_FAIL, Boolean.TYPE, "999");
207 
208         _verifyFailFromInteger(MAPPER_TO_FAIL, AtomicBoolean.class, "0");
209         _verifyFailFromInteger(MAPPER_TO_FAIL, AtomicBoolean.class, "-123");
210     }
211 
212     /*
213     /**********************************************************
214     /* Helper methods
215     /**********************************************************
216      */
217 
_verifyBooleanCoerceFail(String doc, boolean useBytes, JsonToken tokenType, String tokenValue, Class<?> targetType)218     private void _verifyBooleanCoerceFail(String doc, boolean useBytes,
219             JsonToken tokenType, String tokenValue, Class<?> targetType) throws IOException
220     {
221         // Test failure for root value: for both byte- and char-backed sources.
222 
223         // [databind#2635]: important, need to use `readValue()` that takes content and NOT
224         // JsonParser, as this forces closing of underlying parser and exposes more issues.
225 
226         final ObjectReader r = LEGACY_NONCOERCING_MAPPER.readerFor(targetType);
227         try {
228             if (useBytes) {
229                 r.readValue(utf8Bytes(doc));
230             } else {
231                 r.readValue(doc);
232             }
233             fail("Should not have allowed coercion");
234         } catch (MismatchedInputException e) {
235             _verifyBooleanCoerceFailReason(e, tokenType, tokenValue);
236         }
237     }
238 
239     @SuppressWarnings("resource")
_verifyBooleanCoerceFailReason(MismatchedInputException e, JsonToken tokenType, String tokenValue)240     private void _verifyBooleanCoerceFailReason(MismatchedInputException e,
241             JsonToken tokenType, String tokenValue) throws IOException
242     {
243         verifyException(e, "Cannot coerce ");
244         verifyException(e, " to `");
245 
246         JsonParser p = (JsonParser) e.getProcessor();
247 
248         assertToken(tokenType, p.currentToken());
249 
250         final String text = p.getText();
251         if (!tokenValue.equals(text)) {
252             String textDesc = (text == null) ? "NULL" : quote(text);
253             fail("Token text ("+textDesc+") via parser of type "+p.getClass().getName()
254                     +" not as expected ("+quote(tokenValue)+")");
255         }
256     }
257 
_verifyFailFromInteger(ObjectMapper m, Class<?> targetType, String doc)258     private void _verifyFailFromInteger(ObjectMapper m, Class<?> targetType, String doc) throws Exception {
259         _verifyFailFromInteger(m, targetType, doc, targetType);
260     }
261 
_verifyFailFromInteger(ObjectMapper m, Class<?> targetType, String doc, Class<?> valueType)262     private void _verifyFailFromInteger(ObjectMapper m, Class<?> targetType, String doc,
263             Class<?> valueType) throws Exception
264     {
265         try {
266             m.readerFor(targetType).readValue(doc);
267             fail("Should not accept Integer for "+targetType.getName()+" by default");
268         } catch (MismatchedInputException e) {
269             verifyException(e, "Cannot coerce Integer value");
270             verifyException(e, "to `"+valueType.getName()+"`");
271         }
272     }
273 }
274