• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.deser;
2 
3 import java.io.IOException;
4 import java.util.*;
5 
6 import com.fasterxml.jackson.core.*;
7 import com.fasterxml.jackson.databind.*;
8 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
9 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
10 
11 /**
12  * This unit test suite tests use of "value" Annotations;
13  * annotations that define actual type (Class) to use for
14  * deserialization.
15  */
16 public class TestValueAnnotations
17     extends BaseMapTest
18 {
19     /*
20     /**********************************************************
21     /* Annotated root classes for @JsonDeserialize#as
22     /**********************************************************
23      */
24 
25     @JsonDeserialize(using=RootStringDeserializer.class)
26     interface RootString {
contents()27         public String contents();
28     }
29 
30     static class RootStringImpl implements RootString
31     {
32         final String _contents;
33 
RootStringImpl(String x)34         public RootStringImpl(String x) { _contents = x; }
35 
36         @Override
contents()37         public String contents() { return _contents; }
contents2()38         public String contents2() { return _contents; }
39     }
40 
41     @JsonDeserialize(as=RootInterfaceImpl.class)
42     interface RootInterface {
getA()43         public String getA();
44     }
45 
46     static class RootInterfaceImpl implements RootInterface {
47         public String a;
48 
RootInterfaceImpl()49         public RootInterfaceImpl() { }
50 
51         @Override
getA()52         public String getA() { return a; }
53     }
54 
55     @SuppressWarnings("serial")
56     @JsonDeserialize(contentAs=RootStringImpl.class)
57     static class RootMap extends HashMap<String,RootStringImpl> { }
58 
59     @SuppressWarnings("serial")
60     @JsonDeserialize(contentAs=RootStringImpl.class)
61     static class RootList extends LinkedList<RootStringImpl> { }
62 
63     @SuppressWarnings("serial")
64     static class RootStringDeserializer
65         extends StdDeserializer<RootString>
66     {
RootStringDeserializer()67         public RootStringDeserializer() { super(RootString.class); }
68 
69         @Override
deserialize(JsonParser p, DeserializationContext ctxt)70         public RootString deserialize(JsonParser p, DeserializationContext ctxt)
71             throws IOException
72         {
73             if (p.hasToken(JsonToken.VALUE_STRING)) {
74                 return new RootStringImpl(p.getText());
75             }
76             return (RootString) ctxt.handleUnexpectedToken(_valueClass, p);
77         }
78     }
79 
80     /*
81     /**********************************************************
82     /* Annotated helper classes for @JsonDeserialize#as
83     /**********************************************************
84      */
85 
86     /* Class for testing valid {@link JsonDeserialize} annotation
87      * with 'as' parameter to define concrete class to deserialize to
88      */
89     final static class CollectionHolder
90     {
91         Collection<String> _strings;
92 
93         /* Default for 'Collection' would probably be ArrayList or so;
94          * let's try to make it a TreeSet instead.
95          */
96         @JsonDeserialize(as=TreeSet.class)
setStrings(Collection<String> s)97         public void setStrings(Collection<String> s)
98         {
99             _strings = s;
100         }
101     }
102 
103     /* Another class for testing valid {@link JsonDeserialize} annotation
104      * with 'as' parameter to define concrete class to deserialize to
105      */
106     final static class MapHolder
107     {
108         // Let's also coerce numbers into Strings here
109         Map<String,String> _data;
110 
111         /* Default for 'Collection' would be HashMap,
112          * let's try to make it a TreeMap instead.
113          */
114         @JsonDeserialize(as=TreeMap.class)
setStrings(Map<String,String> s)115         public void setStrings(Map<String,String> s)
116         {
117             _data = s;
118         }
119     }
120 
121     /* Another class for testing valid {@link JsonDeserialize} annotation
122      * with 'as' parameter, but with array
123      */
124     final static class ArrayHolder
125     {
126         String[] _strings;
127 
128         @JsonDeserialize(as=String[].class)
setStrings(Object[] o)129         public void setStrings(Object[] o)
130         {
131             // should be passed instances of proper type, as per annotation
132             _strings = (String[]) o;
133         }
134     }
135 
136     /* Another class for testing broken {@link JsonDeserialize} annotation
137      * with 'as' parameter; one with incompatible type
138      */
139     final static class BrokenCollectionHolder
140     {
141         @JsonDeserialize(as=String.class) // not assignable to Collection
setStrings(Collection<String> s)142         public void setStrings(Collection<String> s) { }
143     }
144 
145     /*
146     /**********************************************************
147     /* Annotated helper classes for @JsonDeserialize.keyAs
148     /**********************************************************
149      */
150 
151     final static class StringWrapper
152     {
153         final String _string;
154 
StringWrapper(String s)155         public StringWrapper(String s) { _string = s; }
156     }
157 
158     final static class MapKeyHolder
159     {
160         Map<Object, String> _map;
161 
162         @JsonDeserialize(keyAs=StringWrapper.class)
setMap(Map<Object,String> m)163         public void setMap(Map<Object,String> m)
164         {
165             // type should be ok, but no need to cast here (won't matter)
166             _map = m;
167         }
168     }
169 
170     final static class BrokenMapKeyHolder
171     {
172         // Invalid: Integer not a sub-class of String
173         @JsonDeserialize(keyAs=Integer.class)
setStrings(Map<String,String> m)174             public void setStrings(Map<String,String> m) { }
175     }
176 
177     /*
178     /**********************************************************
179     /* Annotated helper classes for @JsonDeserialize#contentAs
180     /**********************************************************
181      */
182 
183     final static class ListContentHolder
184     {
185         List<?> _list;
186 
187         @JsonDeserialize(contentAs=StringWrapper.class)
setList(List<?> l)188         public void setList(List<?> l) {
189             _list = l;
190         }
191     }
192 
193     // for [databind#2553]
194     @SuppressWarnings("rawtypes")
195     static class List2553 {
196         @JsonDeserialize(contentAs = Item2553.class)
197         public List items;
198     }
199 
200     static class Item2553 {
201         public String name;
202     }
203 
204     final static class InvalidContentClass
205     {
206         /* Such annotation not allowed, since it makes no sense;
207          * non-container classes have no contents to annotate (but
208          * note that it is possible to first use @JsonDesiarialize.as
209          * to mark Object as, say, a List, and THEN use
210          * @JsonDeserialize.contentAs!)
211          */
212         @JsonDeserialize(contentAs=String.class)
setValue(Object x)213             public void setValue(Object x) { }
214     }
215 
216     final static class ArrayContentHolder
217     {
218         Object[] _data;
219 
220         @JsonDeserialize(contentAs=Long.class)
setData(Object[] o)221         public void setData(Object[] o)
222         { // should have proper type, but no need to coerce here
223             _data = o;
224         }
225     }
226 
227     final static class MapContentHolder
228     {
229         Map<Object,Object> _map;
230 
231         @JsonDeserialize(contentAs=Integer.class)
setMap(Map<Object,Object> m)232         public void setMap(Map<Object,Object> m)
233         {
234             _map = m;
235         }
236     }
237 
238     /*
239     /**********************************************************
240     /* Test methods for @JsonDeserialize#as
241     /**********************************************************
242      */
243 
244     private final ObjectMapper MAPPER = newJsonMapper();
245 
testOverrideClassValid()246     public void testOverrideClassValid() throws Exception
247     {
248         CollectionHolder result = MAPPER.readValue
249             ("{ \"strings\" : [ \"test\" ] }", CollectionHolder.class);
250 
251         Collection<String> strs = result._strings;
252         assertEquals(1, strs.size());
253         assertEquals(TreeSet.class, strs.getClass());
254         assertEquals("test", strs.iterator().next());
255     }
256 
testOverrideMapValid()257     public void testOverrideMapValid() throws Exception
258     {
259         // note: expecting conversion from number to String, as well
260         MapHolder result = MAPPER.readValue
261             ("{ \"strings\" :  { \"a\" : 3 } }", MapHolder.class);
262 
263         Map<String,String> strs = result._data;
264         assertEquals(1, strs.size());
265         assertEquals(TreeMap.class, strs.getClass());
266         String value = strs.get("a");
267         assertEquals("3", value);
268     }
269 
testOverrideArrayClass()270     public void testOverrideArrayClass() throws Exception
271     {
272         ArrayHolder result = MAPPER.readValue
273             ("{ \"strings\" : [ \"test\" ] }", ArrayHolder.class);
274 
275         String[] strs = result._strings;
276         assertEquals(1, strs.length);
277         assertEquals(String[].class, strs.getClass());
278         assertEquals("test", strs[0]);
279     }
280 
testOverrideClassInvalid()281     public void testOverrideClassInvalid() throws Exception
282     {
283         // should fail due to incompatible Annotation
284         try {
285             BrokenCollectionHolder result = MAPPER.readValue
286                 ("{ \"strings\" : [ ] }", BrokenCollectionHolder.class);
287             fail("Expected a failure, but got results: "+result);
288         } catch (JsonMappingException jme) {
289             verifyException(jme, "not subtype of");
290         }
291     }
292 
293     /*
294     /**********************************************************
295     /* Test methods for @JsonDeserialize#as used for root values
296     /**********************************************************
297      */
298 
testRootInterfaceAs()299     public void testRootInterfaceAs() throws Exception
300     {
301         RootInterface value = MAPPER.readValue("{\"a\":\"abc\" }", RootInterface.class);
302         assertTrue(value instanceof RootInterfaceImpl);
303         assertEquals("abc", value.getA());
304     }
305 
testRootInterfaceUsing()306     public void testRootInterfaceUsing() throws Exception
307     {
308         RootString value = MAPPER.readValue("\"xxx\"", RootString.class);
309         assertTrue(value instanceof RootString);
310         assertEquals("xxx", value.contents());
311     }
312 
testRootListAs()313     public void testRootListAs() throws Exception
314     {
315         RootMap value = MAPPER.readValue("{\"a\":\"b\"}", RootMap.class);
316         assertEquals(1, value.size());
317         Object v2 = value.get("a");
318         assertEquals(RootStringImpl.class, v2.getClass());
319         assertEquals("b", ((RootString) v2).contents());
320     }
321 
testRootMapAs()322     public void testRootMapAs() throws Exception
323     {
324         RootList value = MAPPER.readValue("[ \"c\" ]", RootList.class);
325         assertEquals(1, value.size());
326         Object v2 = value.get(0);
327         assertEquals(RootStringImpl.class, v2.getClass());
328         assertEquals("c", ((RootString) v2).contents());
329     }
330 
331     /*
332     /**********************************************************
333     /* Test methods for @JsonDeserialize#keyAs
334     /**********************************************************
335      */
336 
337     @SuppressWarnings("unchecked")
testOverrideKeyClassValid()338 	public void testOverrideKeyClassValid() throws Exception
339     {
340         MapKeyHolder result = MAPPER.readValue("{ \"map\" : { \"xxx\" : \"yyy\" } }", MapKeyHolder.class);
341         Map<StringWrapper, String> map = (Map<StringWrapper,String>)(Map<?,?>)result._map;
342         assertEquals(1, map.size());
343         Map.Entry<StringWrapper, String> en = map.entrySet().iterator().next();
344 
345         StringWrapper key = en.getKey();
346         assertEquals(StringWrapper.class, key.getClass());
347         assertEquals("xxx", key._string);
348         assertEquals("yyy", en.getValue());
349     }
350 
testOverrideKeyClassInvalid()351     public void testOverrideKeyClassInvalid() throws Exception
352     {
353         // should fail due to incompatible Annotation
354         try {
355             BrokenMapKeyHolder result = MAPPER.readValue
356                 ("{ \"123\" : \"xxx\" }", BrokenMapKeyHolder.class);
357             fail("Expected a failure, but got results: "+result);
358         } catch (JsonMappingException jme) {
359             verifyException(jme, "not subtype of");
360         }
361     }
362 
363     /*
364     /**********************************************************
365     /* Test methods for @JsonDeserialize#contentAs
366     /**********************************************************
367      */
368 
369     @SuppressWarnings("unchecked")
testOverrideContentClassValid()370     public void testOverrideContentClassValid() throws Exception
371     {
372         ListContentHolder result = MAPPER.readValue("{ \"list\" : [ \"abc\" ] }", ListContentHolder.class);
373         List<StringWrapper> list = (List<StringWrapper>)result._list;
374         assertEquals(1, list.size());
375         Object value = list.get(0);
376         assertEquals(StringWrapper.class, value.getClass());
377         assertEquals("abc", ((StringWrapper) value)._string);
378     }
379 
testOverrideArrayContents()380     public void testOverrideArrayContents() throws Exception
381     {
382         ArrayContentHolder result = MAPPER.readValue("{ \"data\" : [ 1, 2, 3 ] }",
383                                                 ArrayContentHolder.class);
384         Object[] data = result._data;
385         assertEquals(3, data.length);
386         assertEquals(Long[].class, data.getClass());
387         assertEquals(1L, data[0]);
388         assertEquals(2L, data[1]);
389         assertEquals(3L, data[2]);
390     }
391 
testOverrideMapContents()392     public void testOverrideMapContents() throws Exception
393     {
394         MapContentHolder result = MAPPER.readValue("{ \"map\" : { \"a\" : 9 } }",
395                                                 MapContentHolder.class);
396         Map<Object,Object> map = result._map;
397         assertEquals(1, map.size());
398         Object ob = map.values().iterator().next();
399         assertEquals(Integer.class, ob.getClass());
400         assertEquals(Integer.valueOf(9), ob);
401     }
402 
403     // [databind#2553]
testRawListTypeContentAs()404     public void testRawListTypeContentAs() throws Exception
405     {
406         List2553 list =  MAPPER.readValue("{\"items\": [{\"name\":\"item1\"}]}", List2553.class);
407         assertEquals(1, list.items.size());
408         Object value = list.items.get(0);
409         assertEquals(Item2553.class, value.getClass());
410         assertEquals("item1", ((Item2553) value).name);
411     }
412 }
413