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