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