• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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