1 package com.fasterxml.jackson.databind.deser; 2 3 import java.util.*; 4 5 import com.fasterxml.jackson.annotation.*; 6 7 import com.fasterxml.jackson.databind.*; 8 9 /** 10 * Unit tests for verifying that {@link JsonAnySetter} annotation 11 * works as expected. 12 */ 13 public class AnySetterTest 14 extends BaseMapTest 15 { 16 static class MapImitator 17 { 18 HashMap<String,Object> _map; 19 MapImitator()20 public MapImitator() { 21 _map = new HashMap<String,Object>(); 22 } 23 24 @JsonAnySetter addEntry(String key, Object value)25 void addEntry(String key, Object value) 26 { 27 _map.put(key, value); 28 } 29 } 30 31 // for [databind#1376] 32 static class MapImitatorDisabled extends MapImitator 33 { 34 @Override 35 @JsonAnySetter(enabled=false) addEntry(String key, Object value)36 void addEntry(String key, Object value) { 37 throw new RuntimeException("Should not get called"); 38 } 39 } 40 41 /** 42 * Let's also verify that it is possible to define different 43 * value: not often useful, but possible. 44 */ 45 static class MapImitatorWithValue 46 { 47 HashMap<String,int[]> _map; 48 MapImitatorWithValue()49 public MapImitatorWithValue() { 50 _map = new HashMap<String,int[]>(); 51 } 52 53 @JsonAnySetter addEntry(String key, int[] value)54 void addEntry(String key, int[] value) 55 { 56 _map.put(key, value); 57 } 58 } 59 60 // Bad; 2 "any setters" 61 static class Broken 62 { 63 @JsonAnySetter addEntry1(String key, Object value)64 void addEntry1(String key, Object value) { } 65 @JsonAnySetter addEntry2(String key, Object value)66 void addEntry2(String key, Object value) { } 67 } 68 69 @JsonIgnoreProperties("dummy") 70 static class Ignored 71 { 72 HashMap<String,Object> map = new HashMap<String,Object>(); 73 74 @JsonIgnore 75 public String bogus; 76 77 @JsonAnySetter addEntry(String key, Object value)78 void addEntry(String key, Object value) 79 { 80 map.put(key, value); 81 } 82 } 83 84 static class Bean744 85 { 86 protected Map<String,Object> additionalProperties; 87 88 @JsonAnySetter addAdditionalProperty(String key, Object value)89 public void addAdditionalProperty(String key, Object value) { 90 if (additionalProperties == null) additionalProperties = new HashMap<String, Object>(); 91 additionalProperties.put(key,value); 92 } 93 setAdditionalProperties(Map<String, Object> additionalProperties)94 public void setAdditionalProperties(Map<String, Object> additionalProperties) { 95 this.additionalProperties = additionalProperties; 96 } 97 98 @JsonAnyGetter getAdditionalProperties()99 public Map<String,Object> getAdditionalProperties() { return additionalProperties; } 100 101 @JsonIgnore getName()102 public String getName() { 103 return (String) additionalProperties.get("name"); 104 } 105 } 106 107 static class Bean797Base 108 { 109 @JsonAnyGetter getUndefinedProperties()110 public Map<String, JsonNode> getUndefinedProperties() { 111 throw new IllegalStateException("Should not call parent version!"); 112 } 113 } 114 115 static class Bean797BaseImpl extends Bean797Base 116 { 117 @Override getUndefinedProperties()118 public Map<String, JsonNode> getUndefinedProperties() { 119 return new HashMap<String, JsonNode>(); 120 } 121 } 122 123 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 124 static abstract class Base { } 125 126 static class Impl extends Base { 127 public String value; 128 Impl()129 public Impl() { } Impl(String v)130 public Impl(String v) { value = v; } 131 } 132 133 static class PolyAnyBean 134 { 135 protected Map<String,Base> props = new HashMap<String,Base>(); 136 137 @JsonAnyGetter props()138 public Map<String,Base> props() { 139 return props; 140 } 141 142 @JsonAnySetter prop(String name, Base value)143 public void prop(String name, Base value) { 144 props.put(name, value); 145 } 146 } 147 148 static class JsonAnySetterOnMap { 149 public int id; 150 151 @JsonAnySetter 152 protected HashMap<String, String> other = new HashMap<String, String>(); 153 154 @JsonAnyGetter any()155 public Map<String, String> any() { 156 return other; 157 } 158 } 159 160 static class JsonAnySetterOnNullMap { 161 public int id; 162 163 @JsonAnySetter 164 protected HashMap<String, String> other; 165 166 @JsonAnyGetter any()167 public Map<String, String> any() { 168 return other; 169 } 170 } 171 172 static class MyGeneric<T> 173 { 174 private String staticallyMappedProperty; 175 private Map<T, Integer> dynamicallyMappedProperties = new HashMap<T, Integer>(); 176 getStaticallyMappedProperty()177 public String getStaticallyMappedProperty() { 178 return staticallyMappedProperty; 179 } 180 181 @JsonAnySetter addDynamicallyMappedProperty(T key, int value)182 public void addDynamicallyMappedProperty(T key, int value) { 183 dynamicallyMappedProperties.put(key, value); 184 } 185 setStaticallyMappedProperty(String staticallyMappedProperty)186 public void setStaticallyMappedProperty(String staticallyMappedProperty) { 187 this.staticallyMappedProperty = staticallyMappedProperty; 188 } 189 190 @JsonAnyGetter getDynamicallyMappedProperties()191 public Map<T, Integer> getDynamicallyMappedProperties() { 192 return dynamicallyMappedProperties; 193 } 194 } 195 196 static class MyWrapper 197 { 198 private MyGeneric<String> myStringGeneric; 199 private MyGeneric<Integer> myIntegerGeneric; 200 getMyStringGeneric()201 public MyGeneric<String> getMyStringGeneric() { 202 return myStringGeneric; 203 } 204 setMyStringGeneric(MyGeneric<String> myStringGeneric)205 public void setMyStringGeneric(MyGeneric<String> myStringGeneric) { 206 this.myStringGeneric = myStringGeneric; 207 } 208 getMyIntegerGeneric()209 public MyGeneric<Integer> getMyIntegerGeneric() { 210 return myIntegerGeneric; 211 } 212 setMyIntegerGeneric(MyGeneric<Integer> myIntegerGeneric)213 public void setMyIntegerGeneric(MyGeneric<Integer> myIntegerGeneric) { 214 this.myIntegerGeneric = myIntegerGeneric; 215 } 216 } 217 218 // [databind#349] 219 static class Bean349 220 { 221 public String type; 222 public int x, y; 223 224 private Map<String, Object> props = new HashMap<>(); 225 226 @JsonAnySetter addProperty(String key, Object value)227 public void addProperty(String key, Object value) { 228 props.put(key, value); 229 } 230 231 @JsonAnyGetter getProperties()232 public Map<String, Object> getProperties() { 233 return props; 234 } 235 236 @JsonUnwrapped 237 public IdentityDTO349 identity; 238 } 239 240 static class IdentityDTO349 { 241 public int x, y; 242 } 243 244 /* 245 /********************************************************** 246 /* Test methods 247 /********************************************************** 248 */ 249 250 private final ObjectMapper MAPPER = new ObjectMapper(); 251 testSimpleMapImitation()252 public void testSimpleMapImitation() throws Exception 253 { 254 MapImitator mapHolder = MAPPER.readValue 255 ("{ \"a\" : 3, \"b\" : true, \"c\":[1,2,3] }", MapImitator.class); 256 Map<String,Object> result = mapHolder._map; 257 assertEquals(3, result.size()); 258 assertEquals(Integer.valueOf(3), result.get("a")); 259 assertEquals(Boolean.TRUE, result.get("b")); 260 Object ob = result.get("c"); 261 assertTrue(ob instanceof List<?>); 262 List<?> l = (List<?>)ob; 263 assertEquals(3, l.size()); 264 assertEquals(Integer.valueOf(3), l.get(2)); 265 } 266 testAnySetterDisable()267 public void testAnySetterDisable() throws Exception 268 { 269 try { 270 MAPPER.readValue(aposToQuotes("{'value':3}"), 271 MapImitatorDisabled.class); 272 fail("Should not pass"); 273 } catch (JsonMappingException e) { 274 verifyException(e, "Unrecognized field \"value\""); 275 } 276 277 } 278 testSimpleTyped()279 public void testSimpleTyped() throws Exception 280 { 281 MapImitatorWithValue mapHolder = MAPPER.readValue 282 ("{ \"a\" : [ 3, -1 ], \"b\" : [ ] }", MapImitatorWithValue.class); 283 Map<String,int[]> result = mapHolder._map; 284 assertEquals(2, result.size()); 285 assertEquals(new int[] { 3, -1 }, result.get("a")); 286 assertEquals(new int[0], result.get("b")); 287 } 288 testBrokenWithDoubleAnnotations()289 public void testBrokenWithDoubleAnnotations() throws Exception 290 { 291 try { 292 @SuppressWarnings("unused") 293 Broken b = MAPPER.readValue("{ \"a\" : 3 }", Broken.class); 294 fail("Should have gotten an exception"); 295 } catch (JsonMappingException e) { 296 verifyException(e, "Multiple 'any-setter' methods"); 297 } 298 } 299 testIgnored()300 public void testIgnored() throws Exception 301 { 302 ObjectMapper mapper = new ObjectMapper(); 303 mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 304 _testIgnorals(mapper); 305 } 306 testIgnoredPart2()307 public void testIgnoredPart2() throws Exception 308 { 309 ObjectMapper mapper = new ObjectMapper(); 310 mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 311 _testIgnorals(mapper); 312 } 313 testProblem744()314 public void testProblem744() throws Exception 315 { 316 Bean744 bean = MAPPER.readValue("{\"name\":\"Bob\"}", Bean744.class); 317 assertNotNull(bean.additionalProperties); 318 assertEquals(1, bean.additionalProperties.size()); 319 assertEquals("Bob", bean.additionalProperties.get("name")); 320 } 321 testIssue797()322 public void testIssue797() throws Exception 323 { 324 String json = MAPPER.writeValueAsString(new Bean797BaseImpl()); 325 assertEquals("{}", json); 326 } 327 328 // [Issue#337] testPolymorphic()329 public void testPolymorphic() throws Exception 330 { 331 PolyAnyBean input = new PolyAnyBean(); 332 input.props.put("a", new Impl("xyz")); 333 334 String json = MAPPER.writeValueAsString(input); 335 336 // System.err.println("JSON: "+json); 337 338 PolyAnyBean result = MAPPER.readValue(json, PolyAnyBean.class); 339 assertEquals(1, result.props.size()); 340 Base ob = result.props.get("a"); 341 assertNotNull(ob); 342 assertTrue(ob instanceof Impl); 343 assertEquals("xyz", ((Impl) ob).value); 344 } 345 testJsonAnySetterOnMap()346 public void testJsonAnySetterOnMap() throws Exception { 347 JsonAnySetterOnMap result = MAPPER.readValue("{\"id\":2,\"name\":\"Joe\", \"city\":\"New Jersey\"}", 348 JsonAnySetterOnMap.class); 349 assertEquals(2, result.id); 350 assertEquals("Joe", result.other.get("name")); 351 assertEquals("New Jersey", result.other.get("city")); 352 } 353 testJsonAnySetterOnNullMap()354 public void testJsonAnySetterOnNullMap() throws Exception { 355 JsonAnySetterOnNullMap result = MAPPER.readValue("{\"id\":2,\"name\":\"Joe\", \"city\":\"New Jersey\"}", 356 JsonAnySetterOnNullMap.class); 357 assertEquals(2, result.id); 358 assertNull(result.other); 359 } 360 361 final static String UNWRAPPED_JSON_349 = aposToQuotes( 362 "{ 'type' : 'IST',\n" 363 +" 'x' : 3,\n" 364 //+" 'name' : 'BLAH-New',\n" 365 //+" 'description' : 'namespace.name: X THIN FIR.DR-WD12-New',\n" 366 +" 'ZoomLinks': [ 'foofoofoofoo', 'barbarbarbar' ],\n" 367 +" 'y' : 4, 'z' : 8 }" 368 ); 369 370 // [databind#349] testUnwrappedWithAny()371 public void testUnwrappedWithAny() throws Exception 372 { 373 final ObjectMapper mapper = objectMapper(); 374 Bean349 value = mapper.readValue(UNWRAPPED_JSON_349, Bean349.class); 375 assertNotNull(value); 376 assertEquals(3, value.x); 377 assertEquals(4, value.y); 378 assertEquals(2, value.props.size()); 379 } 380 381 // [databind#349] testUnwrappedWithAnyAsUpdate()382 public void testUnwrappedWithAnyAsUpdate() throws Exception 383 { 384 final ObjectMapper mapper = objectMapper(); 385 Bean349 bean = mapper.readerFor(Bean349.class) 386 .withValueToUpdate(new Bean349()) 387 .readValue(UNWRAPPED_JSON_349); 388 assertEquals(3, bean.x); 389 assertEquals(4, bean.y); 390 assertEquals(2, bean.props.size()); 391 } 392 393 // [databind#1035] testGenericAnySetter()394 public void testGenericAnySetter() throws Exception 395 { 396 ObjectMapper mapper = new ObjectMapper(); 397 398 Map<String, Integer> stringGenericMap = new HashMap<String, Integer>(); 399 stringGenericMap.put("testStringKey", 5); 400 Map<Integer, Integer> integerGenericMap = new HashMap<Integer, Integer>(); 401 integerGenericMap.put(111, 6); 402 403 MyWrapper deserialized = mapper.readValue(aposToQuotes( 404 "{'myStringGeneric':{'staticallyMappedProperty':'Test','testStringKey':5},'myIntegerGeneric':{'staticallyMappedProperty':'Test2','111':6}}" 405 ), MyWrapper.class); 406 MyGeneric<String> stringGeneric = deserialized.getMyStringGeneric(); 407 MyGeneric<Integer> integerGeneric = deserialized.getMyIntegerGeneric(); 408 409 assertNotNull(stringGeneric); 410 assertEquals(stringGeneric.getStaticallyMappedProperty(), "Test"); 411 for(Map.Entry<String, Integer> entry : stringGeneric.getDynamicallyMappedProperties().entrySet()) { 412 assertTrue("A key in MyGeneric<String> is not an String.", entry.getKey() instanceof String); 413 assertTrue("A value in MyGeneric<Integer> is not an Integer.", entry.getValue() instanceof Integer); 414 } 415 assertEquals(stringGeneric.getDynamicallyMappedProperties(), stringGenericMap); 416 417 assertNotNull(integerGeneric); 418 assertEquals(integerGeneric.getStaticallyMappedProperty(), "Test2"); 419 for(Map.Entry<Integer, Integer> entry : integerGeneric.getDynamicallyMappedProperties().entrySet()) { 420 Object key = entry.getKey(); 421 assertEquals("A key in MyGeneric<Integer> is not an Integer.", Integer.class, key.getClass()); 422 Object value = entry.getValue(); 423 assertEquals("A value in MyGeneric<Integer> is not an Integer.", Integer.class, value.getClass()); 424 } 425 assertEquals(integerGeneric.getDynamicallyMappedProperties(), integerGenericMap); 426 } 427 428 /* 429 /********************************************************** 430 /* Private helper methods 431 /********************************************************** 432 */ 433 _testIgnorals(ObjectMapper mapper)434 private void _testIgnorals(ObjectMapper mapper) throws Exception 435 { 436 Ignored bean = mapper.readValue("{\"name\":\"Bob\", \"bogus\": [ 1, 2, 3], \"dummy\" : 13 }", Ignored.class); 437 // as of 2.0, @JsonIgnoreProperties does block; @JsonIgnore not 438 assertNull(bean.map.get("dummy")); 439 assertEquals("[1, 2, 3]", ""+bean.map.get("bogus")); 440 assertEquals("Bob", bean.map.get("name")); 441 assertEquals(2, bean.map.size()); 442 } 443 } 444