1 package com.fasterxml.jackson.databind.deser; 2 3 import java.io.*; 4 import java.lang.annotation.*; 5 import java.util.*; 6 7 import com.fasterxml.jackson.annotation.JsonCreator; 8 import com.fasterxml.jackson.annotation.JsonProperty; 9 10 import com.fasterxml.jackson.core.*; 11 12 import com.fasterxml.jackson.databind.*; 13 import com.fasterxml.jackson.databind.annotation.*; 14 import com.fasterxml.jackson.databind.deser.std.*; 15 import com.fasterxml.jackson.databind.module.SimpleModule; 16 import com.fasterxml.jackson.databind.node.ArrayNode; 17 import com.fasterxml.jackson.databind.util.StdConverter; 18 19 /** 20 * Test to check that customizations work as expected. 21 */ 22 @SuppressWarnings("serial") 23 public class TestCustomDeserializers 24 extends BaseMapTest 25 { 26 static class DummyDeserializer<T> 27 extends StdDeserializer<T> 28 { 29 final T value; 30 DummyDeserializer(T v, Class<T> cls)31 public DummyDeserializer(T v, Class<T> cls) { 32 super(cls); 33 value = v; 34 } 35 36 @Override deserialize(JsonParser p, DeserializationContext ctxt)37 public T deserialize(JsonParser p, DeserializationContext ctxt) 38 throws IOException 39 { 40 // need to skip, if structured... 41 p.skipChildren(); 42 return value; 43 } 44 } 45 46 static class TestBeans { 47 public List<TestBean> beans; 48 } 49 static class TestBean { 50 public CustomBean c; 51 public String d; 52 } 53 @JsonDeserialize(using=CustomBeanDeserializer.class) 54 static class CustomBean { 55 protected final int a, b; CustomBean(int a, int b)56 public CustomBean(int a, int b) { 57 this.a = a; 58 this.b = b; 59 } 60 } 61 62 static class CustomBeanDeserializer extends JsonDeserializer<CustomBean> 63 { 64 @Override deserialize(JsonParser p, DeserializationContext ctxt)65 public CustomBean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException 66 { 67 int a = 0, b = 0; 68 JsonToken t = p.currentToken(); 69 if (t == JsonToken.START_OBJECT) { 70 t = p.nextToken(); 71 } else if (t != JsonToken.FIELD_NAME) { 72 throw new Error(); 73 } 74 while(t == JsonToken.FIELD_NAME) { 75 final String fieldName = p.currentName(); 76 t = p.nextToken(); 77 if (t != JsonToken.VALUE_NUMBER_INT) { 78 throw new JsonParseException(p, "expecting number got "+ t); 79 } 80 if (fieldName.equals("a")) { 81 a = p.getIntValue(); 82 } else if (fieldName.equals("b")) { 83 b = p.getIntValue(); 84 } else { 85 throw new Error(); 86 } 87 t = p.nextToken(); 88 } 89 return new CustomBean(a, b); 90 } 91 } 92 93 public static class Immutable { 94 protected int x, y; 95 Immutable(int x0, int y0)96 public Immutable(int x0, int y0) { 97 x = x0; 98 y = y0; 99 } 100 } 101 102 public static class CustomKey { 103 private final int id; 104 CustomKey(int id)105 public CustomKey(int id) {this.id = id;} 106 getId()107 public int getId() { return id; } 108 } 109 110 public static class Model 111 { 112 protected final Map<CustomKey, String> map; 113 114 @JsonCreator Model(@sonProperty"map") @sonDeserializekeyUsing = CustomKeyDeserializer.class) Map<CustomKey, String> map)115 public Model(@JsonProperty("map") @JsonDeserialize(keyUsing = CustomKeyDeserializer.class) Map<CustomKey, String> map) 116 { 117 this.map = new HashMap<CustomKey, String>(map); 118 } 119 120 @JsonProperty 121 @JsonSerialize(keyUsing = CustomKeySerializer.class) getMap()122 public Map<CustomKey, String> getMap() { 123 return map; 124 } 125 } 126 127 static class CustomKeySerializer extends JsonSerializer<CustomKey> { 128 @Override serialize(CustomKey value, JsonGenerator g, SerializerProvider provider)129 public void serialize(CustomKey value, JsonGenerator g, SerializerProvider provider) throws IOException { 130 g.writeFieldName(String.valueOf(value.getId())); 131 } 132 } 133 134 static class CustomKeyDeserializer extends KeyDeserializer { 135 @Override deserializeKey(String key, DeserializationContext ctxt)136 public CustomKey deserializeKey(String key, DeserializationContext ctxt) throws IOException { 137 return new CustomKey(Integer.valueOf(key)); 138 } 139 } 140 141 // [databind#375] 142 143 @Target({ElementType.FIELD}) 144 @Retention(RetentionPolicy.RUNTIME) 145 @interface Negative { } 146 147 static class Bean375Wrapper { 148 @Negative 149 public Bean375Outer value; 150 } 151 152 static class Bean375Outer { 153 protected Bean375Inner inner; 154 Bean375Outer(Bean375Inner v)155 public Bean375Outer(Bean375Inner v) { inner = v; } 156 } 157 158 static class Bean375Inner { 159 protected int x; 160 Bean375Inner(int x)161 public Bean375Inner(int x) { this.x = x; } 162 } 163 164 static class Bean375OuterDeserializer extends StdDeserializer<Bean375Outer> 165 implements ContextualDeserializer 166 { 167 protected BeanProperty prop; 168 Bean375OuterDeserializer()169 protected Bean375OuterDeserializer() { this(null); } Bean375OuterDeserializer(BeanProperty p)170 protected Bean375OuterDeserializer(BeanProperty p) { 171 super(Bean375Outer.class); 172 prop = p; 173 } 174 175 @Override deserialize(JsonParser p, DeserializationContext ctxt)176 public Bean375Outer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, 177 JsonProcessingException { 178 Object ob = ctxt.readPropertyValue(p, prop, Bean375Inner.class); 179 return new Bean375Outer((Bean375Inner) ob); 180 } 181 @Override createContextual(DeserializationContext ctxt, BeanProperty property)182 public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) 183 throws JsonMappingException { 184 return new Bean375OuterDeserializer(property); 185 } 186 } 187 188 static class Bean375InnerDeserializer extends StdDeserializer<Bean375Inner> 189 implements ContextualDeserializer 190 { 191 protected boolean negative; 192 Bean375InnerDeserializer()193 protected Bean375InnerDeserializer() { this(false); } Bean375InnerDeserializer(boolean n)194 protected Bean375InnerDeserializer(boolean n) { 195 super(Bean375Inner.class); 196 negative = n; 197 } 198 199 @Override deserialize(JsonParser p, DeserializationContext ctxt)200 public Bean375Inner deserialize(JsonParser p, DeserializationContext ctxt) 201 throws IOException, JsonProcessingException { 202 int x = p.getIntValue(); 203 if (negative) { 204 x = -x; 205 } else { 206 x += x; 207 } 208 return new Bean375Inner(x); 209 } 210 211 @Override createContextual(DeserializationContext ctxt, BeanProperty property)212 public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) 213 throws JsonMappingException { 214 if (property != null) { 215 Negative n = property.getAnnotation(Negative.class); 216 if (n != null) { 217 return new Bean375InnerDeserializer(true); 218 } 219 } 220 return this; 221 } 222 } 223 224 // for [databind#631] 225 static class Issue631Bean 226 { 227 @JsonDeserialize(using=ParentClassDeserializer.class) 228 public Object prop; 229 } 230 231 static class ParentClassDeserializer 232 extends StdScalarDeserializer<Object> 233 { ParentClassDeserializer()234 protected ParentClassDeserializer() { 235 super(Object.class); 236 } 237 238 @Override deserialize(JsonParser p, DeserializationContext ctxt)239 public Object deserialize(JsonParser p, DeserializationContext ctxt) 240 throws IOException { 241 Object parent = p.getCurrentValue(); 242 String desc = (parent == null) ? "NULL" : parent.getClass().getSimpleName(); 243 return "prop/"+ desc; 244 } 245 } 246 247 static class UCStringDeserializer extends StdDeserializer<String> { UCStringDeserializer()248 public UCStringDeserializer() { super(String.class); } 249 250 @Override deserialize(JsonParser p, DeserializationContext ctxt)251 public String deserialize(JsonParser p, DeserializationContext ctxt) 252 throws IOException { 253 return p.getText().toUpperCase(); 254 } 255 } 256 257 static class DelegatingModuleImpl extends SimpleModule 258 { DelegatingModuleImpl()259 public DelegatingModuleImpl() { 260 super("test", Version.unknownVersion()); 261 } 262 263 @Override setupModule(SetupContext context)264 public void setupModule(SetupContext context) 265 { 266 super.setupModule(context); 267 context.addBeanDeserializerModifier(new BeanDeserializerModifier() { 268 @Override 269 public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, 270 BeanDescription beanDesc, JsonDeserializer<?> deserializer) { 271 if (deserializer.handledType() == String.class) { 272 JsonDeserializer<?> d = new MyStringDeserializer(deserializer); 273 // just for test coverage purposes... 274 if (d.getDelegatee() != deserializer) { 275 throw new Error("Cannot access delegatee!"); 276 } 277 return d; 278 } 279 return deserializer; 280 } 281 }); 282 } 283 } 284 285 static class MyStringDeserializer extends DelegatingDeserializer 286 { MyStringDeserializer(JsonDeserializer<?> newDel)287 public MyStringDeserializer(JsonDeserializer<?> newDel) { 288 super(newDel); 289 } 290 291 @Override newDelegatingInstance(JsonDeserializer<?> newDel)292 protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDel) { 293 return new MyStringDeserializer(newDel); 294 } 295 296 @Override deserialize(JsonParser p, DeserializationContext ctxt)297 public Object deserialize(JsonParser p, DeserializationContext ctxt) 298 throws IOException 299 { 300 Object ob = _delegatee.deserialize(p, ctxt); 301 return "MY:"+ob; 302 } 303 } 304 305 static class MyNodeDeserializer extends StdDeserializer<Object> { MyNodeDeserializer()306 public MyNodeDeserializer() { super(Object.class); } 307 308 @Override deserialize(JsonParser p, DeserializationContext ctxt)309 public Object deserialize(JsonParser p, DeserializationContext ctxt) 310 throws IOException { 311 return ctxt.readTree(p); 312 } 313 } 314 315 /* 316 /********************************************************** 317 /* Unit tests 318 /********************************************************** 319 */ 320 321 final ObjectMapper MAPPER = objectMapper(); 322 testCustomBeanDeserializer()323 public void testCustomBeanDeserializer() throws Exception 324 { 325 String json = "{\"beans\":[{\"c\":{\"a\":10,\"b\":20},\"d\":\"hello, tatu\"}]}"; 326 TestBeans beans = MAPPER.readValue(json, TestBeans.class); 327 328 assertNotNull(beans); 329 List<TestBean> results = beans.beans; 330 assertNotNull(results); 331 assertEquals(1, results.size()); 332 TestBean bean = results.get(0); 333 assertEquals("hello, tatu", bean.d); 334 CustomBean c = bean.c; 335 assertNotNull(c); 336 assertEquals(10, c.a); 337 assertEquals(20, c.b); 338 339 json = "{\"beans\":[{\"c\":{\"b\":3,\"a\":-4},\"d\":\"\"}," 340 +"{\"d\":\"abc\", \"c\":{\"b\":15}}]}"; 341 beans = MAPPER.readValue(json, TestBeans.class); 342 343 assertNotNull(beans); 344 results = beans.beans; 345 assertNotNull(results); 346 assertEquals(2, results.size()); 347 348 bean = results.get(0); 349 assertEquals("", bean.d); 350 c = bean.c; 351 assertNotNull(c); 352 assertEquals(-4, c.a); 353 assertEquals(3, c.b); 354 355 bean = results.get(1); 356 assertEquals("abc", bean.d); 357 c = bean.c; 358 assertNotNull(c); 359 assertEquals(0, c.a); 360 assertEquals(15, c.b); 361 } 362 363 // [Issue#87]: delegating deserializer testDelegating()364 public void testDelegating() throws Exception 365 { 366 ObjectMapper mapper = new ObjectMapper(); 367 SimpleModule module = new SimpleModule("test", Version.unknownVersion()); 368 module.addDeserializer(Immutable.class, 369 new StdDelegatingDeserializer<Immutable>( 370 new StdConverter<JsonNode, Immutable>() { 371 @Override 372 public Immutable convert(JsonNode value) 373 { 374 int x = value.path("x").asInt(); 375 int y = value.path("y").asInt(); 376 return new Immutable(x, y); 377 } 378 } 379 )); 380 381 mapper.registerModule(module); 382 Immutable imm = mapper.readValue("{\"x\":3,\"y\":7}", Immutable.class); 383 assertEquals(3, imm.x); 384 assertEquals(7, imm.y); 385 } 386 387 // [databind#623] testJsonNodeDelegating()388 public void testJsonNodeDelegating() throws Exception 389 { 390 ObjectMapper mapper = new ObjectMapper(); 391 SimpleModule module = new SimpleModule("test", Version.unknownVersion()); 392 module.addDeserializer(Immutable.class, 393 new StdNodeBasedDeserializer<Immutable>(Immutable.class) { 394 @Override 395 public Immutable convert(JsonNode root, DeserializationContext ctxt) throws IOException { 396 int x = root.path("x").asInt(); 397 int y = root.path("y").asInt(); 398 return new Immutable(x, y); 399 } 400 }); 401 mapper.registerModule(module); 402 Immutable imm = mapper.readValue("{\"x\":-10,\"y\":3}", Immutable.class); 403 assertEquals(-10, imm.x); 404 assertEquals(3, imm.y); 405 } 406 testIssue882()407 public void testIssue882() throws Exception 408 { 409 Model original = new Model(Collections.singletonMap(new CustomKey(123), "test")); 410 String json = MAPPER.writeValueAsString(original); 411 Model deserialized = MAPPER.readValue(json, Model.class); 412 assertNotNull(deserialized); 413 assertNotNull(deserialized.map); 414 assertEquals(1, deserialized.map.size()); 415 } 416 417 // [#337]: convenience methods for custom deserializers to use testContextReadValue()418 public void testContextReadValue() throws Exception 419 { 420 ObjectMapper mapper = new ObjectMapper(); 421 SimpleModule module = new SimpleModule("test", Version.unknownVersion()); 422 module.addDeserializer(Bean375Outer.class, new Bean375OuterDeserializer()); 423 module.addDeserializer(Bean375Inner.class, new Bean375InnerDeserializer()); 424 mapper.registerModule(module); 425 426 // First, without property; doubles up value: 427 Bean375Outer outer = mapper.readValue("13", Bean375Outer.class); 428 assertEquals(26, outer.inner.x); 429 430 // then with property; should find annotation, turn negative 431 Bean375Wrapper w = mapper.readValue("{\"value\":13}", Bean375Wrapper.class); 432 assertNotNull(w.value); 433 assertNotNull(w.value.inner); 434 assertEquals(-13, w.value.inner.x); 435 } 436 437 // [#631]: "current value" access testCurrentValueAccess()438 public void testCurrentValueAccess() throws Exception 439 { 440 Issue631Bean bean = MAPPER.readValue(aposToQuotes("{'prop':'stuff'}"), 441 Issue631Bean.class); 442 assertNotNull(bean); 443 assertEquals("prop/Issue631Bean", bean.prop); 444 } 445 testCustomStringDeser()446 public void testCustomStringDeser() throws Exception 447 { 448 ObjectMapper mapper = new ObjectMapper().registerModule( 449 new SimpleModule().addDeserializer(String.class, new UCStringDeserializer()) 450 ); 451 assertEquals("FOO", mapper.readValue(quote("foo"), String.class)); 452 StringWrapper sw = mapper.readValue("{\"str\":\"foo\"}", StringWrapper.class); 453 assertNotNull(sw); 454 assertEquals("FOO", sw.str); 455 } 456 testDelegatingDeserializer()457 public void testDelegatingDeserializer() throws Exception 458 { 459 ObjectMapper mapper = new ObjectMapper().registerModule( 460 new DelegatingModuleImpl()); 461 String str = mapper.readValue(quote("foo"), String.class); 462 assertEquals("MY:foo", str); 463 } 464 465 // [databind#2392] testModifyingCustomDeserializer()466 public void testModifyingCustomDeserializer() throws Exception 467 { 468 ObjectMapper mapper = jsonMapperBuilder() 469 .addModule(new SimpleModule() 470 .setDeserializerModifier(new BeanDeserializerModifier() { 471 @Override 472 public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, 473 BeanDescription beanDesc, JsonDeserializer<?> deserializer) { 474 if (deserializer instanceof DummyDeserializer<?>) { 475 return new DummyDeserializer<String>("FOOBAR", String.class); 476 } 477 return deserializer; 478 } 479 }) 480 .addDeserializer(String.class, new DummyDeserializer<String>("dummy", String.class)) 481 ).build(); 482 String str = mapper.readValue(quote("foo"), String.class); 483 assertEquals("FOOBAR", str); 484 } 485 486 // [databind#2452] testCustomSerializerWithReadTree()487 public void testCustomSerializerWithReadTree() throws Exception 488 { 489 ObjectMapper mapper = jsonMapperBuilder() 490 .addModule(new SimpleModule() 491 .addDeserializer(Object.class, new MyNodeDeserializer()) 492 ) 493 .build(); 494 ObjectWrapper w = mapper.readValue(aposToQuotes("[ 1, { 'a' : 3}, 123 ] "), 495 ObjectWrapper.class); 496 assertEquals(ArrayNode.class, w.getObject().getClass()); 497 JsonNode n = (JsonNode) w.getObject(); 498 assertEquals(3, n.size()); 499 assertEquals(123, n.get(2).intValue()); 500 } 501 } 502