1 package com.fasterxml.jackson.databind.contextual; 2 3 import java.io.IOException; 4 import java.lang.annotation.ElementType; 5 import java.lang.annotation.Retention; 6 import java.lang.annotation.RetentionPolicy; 7 import java.lang.annotation.Target; 8 import java.util.*; 9 10 import com.fasterxml.jackson.annotation.*; 11 import com.fasterxml.jackson.core.JsonParser; 12 import com.fasterxml.jackson.core.Version; 13 import com.fasterxml.jackson.databind.*; 14 import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 15 import com.fasterxml.jackson.databind.deser.ContextualDeserializer; 16 import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; 17 import com.fasterxml.jackson.databind.json.JsonMapper; 18 import com.fasterxml.jackson.databind.module.SimpleModule; 19 20 /** 21 * Test cases to verify that it is possible to define deserializers 22 * that can use contextual information (like field/method 23 * annotations) for configuration. 24 */ 25 @SuppressWarnings("serial") 26 public class TestContextualDeserialization extends BaseMapTest 27 { 28 @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) 29 @Retention(RetentionPolicy.RUNTIME) 30 @JacksonAnnotation 31 public @interface Name { value()32 public String value(); 33 } 34 35 static class StringValue { 36 protected String value; 37 StringValue(String v)38 public StringValue(String v) { value = v; } 39 } 40 41 static class ContextualBean 42 { 43 @Name("NameA") 44 public StringValue a; 45 @Name("NameB") 46 public StringValue b; 47 } 48 49 static class ContextualCtorBean 50 { 51 protected String a, b; 52 53 @JsonCreator ContextualCtorBean( @ame"CtorA") @sonProperty"a") StringValue a, @Name("CtorB") @JsonProperty("b") StringValue b)54 public ContextualCtorBean( 55 @Name("CtorA") @JsonProperty("a") StringValue a, 56 @Name("CtorB") @JsonProperty("b") StringValue b) 57 { 58 this.a = a.value; 59 this.b = b.value; 60 } 61 } 62 63 @Name("Class") 64 static class ContextualClassBean 65 { 66 public StringValue a; 67 68 @Name("NameB") 69 public StringValue b; 70 } 71 72 static class ContextualArrayBean 73 { 74 @Name("array") 75 public StringValue[] beans; 76 } 77 78 static class ContextualListBean 79 { 80 @Name("list") 81 public List<StringValue> beans; 82 } 83 84 static class ContextualMapBean 85 { 86 @Name("map") 87 public Map<String, StringValue> beans; 88 } 89 90 static class MyContextualDeserializer 91 extends JsonDeserializer<StringValue> 92 implements ContextualDeserializer 93 { 94 protected final String _fieldName; 95 MyContextualDeserializer()96 public MyContextualDeserializer() { this(""); } MyContextualDeserializer(String fieldName)97 public MyContextualDeserializer(String fieldName) { 98 _fieldName = fieldName; 99 } 100 101 @Override deserialize(JsonParser jp, DeserializationContext ctxt)102 public StringValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException 103 { 104 return new StringValue(""+_fieldName+"="+jp.getText()); 105 } 106 107 @Override createContextual(DeserializationContext ctxt, BeanProperty property)108 public JsonDeserializer<?> createContextual(DeserializationContext ctxt, 109 BeanProperty property) 110 throws JsonMappingException 111 { 112 String name = (property == null) ? "NULL" : property.getName(); 113 return new MyContextualDeserializer(name); 114 } 115 } 116 117 /** 118 * Alternative that uses annotation for choosing name to use 119 */ 120 static class AnnotatedContextualDeserializer 121 extends JsonDeserializer<StringValue> 122 implements ContextualDeserializer 123 { 124 protected final String _fieldName; 125 AnnotatedContextualDeserializer()126 public AnnotatedContextualDeserializer() { this(""); } AnnotatedContextualDeserializer(String fieldName)127 public AnnotatedContextualDeserializer(String fieldName) { 128 _fieldName = fieldName; 129 } 130 131 @Override deserialize(JsonParser jp, DeserializationContext ctxt)132 public StringValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException 133 { 134 return new StringValue(""+_fieldName+"="+jp.getText()); 135 } 136 137 @Override createContextual(DeserializationContext ctxt, BeanProperty property)138 public JsonDeserializer<?> createContextual(DeserializationContext ctxt, 139 BeanProperty property) 140 throws JsonMappingException 141 { 142 Name ann = property.getAnnotation(Name.class); 143 if (ann == null) { 144 ann = property.getContextAnnotation(Name.class); 145 } 146 String propertyName = (ann == null) ? "UNKNOWN" : ann.value(); 147 return new MyContextualDeserializer(propertyName); 148 } 149 } 150 151 static class GenericStringDeserializer 152 extends StdScalarDeserializer<Object> 153 implements ContextualDeserializer 154 { 155 final String _value; 156 GenericStringDeserializer()157 public GenericStringDeserializer() { this("N/A"); } GenericStringDeserializer(String value)158 protected GenericStringDeserializer(String value) { 159 super(String.class); 160 _value = value; 161 } 162 163 @Override createContextual(DeserializationContext ctxt, BeanProperty property)164 public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) { 165 return new GenericStringDeserializer(String.valueOf(ctxt.getContextualType().getRawClass().getSimpleName())); 166 } 167 168 @Override deserialize(JsonParser p, DeserializationContext ctxt)169 public Object deserialize(JsonParser p, DeserializationContext ctxt) { 170 return _value; 171 } 172 } 173 174 static class GenericBean { 175 @JsonDeserialize(contentUsing=GenericStringDeserializer.class) 176 public Map<Integer, String> stuff; 177 } 178 179 /* 180 /********************************************************** 181 /* Unit tests 182 /********************************************************** 183 */ 184 185 private final ObjectMapper ANNOTATED_CTXT_MAPPER = JsonMapper.builder() 186 .addModule(new SimpleModule("test", Version.unknownVersion()) 187 .addDeserializer(StringValue.class, new AnnotatedContextualDeserializer() 188 )) 189 .build(); 190 testSimple()191 public void testSimple() throws Exception 192 { 193 ObjectMapper mapper = new ObjectMapper(); 194 SimpleModule module = new SimpleModule("test", Version.unknownVersion()); 195 module.addDeserializer(StringValue.class, new MyContextualDeserializer()); 196 mapper.registerModule(module); 197 ContextualBean bean = mapper.readValue("{\"a\":\"1\",\"b\":\"2\"}", ContextualBean.class); 198 assertEquals("a=1", bean.a.value); 199 assertEquals("b=2", bean.b.value); 200 201 // try again, to ensure caching etc works 202 bean = mapper.readValue("{\"a\":\"3\",\"b\":\"4\"}", ContextualBean.class); 203 assertEquals("a=3", bean.a.value); 204 assertEquals("b=4", bean.b.value); 205 } 206 testSimpleWithAnnotations()207 public void testSimpleWithAnnotations() throws Exception 208 { 209 ObjectMapper mapper = _mapperWithAnnotatedContextual(); 210 ContextualBean bean = mapper.readValue("{\"a\":\"1\",\"b\":\"2\"}", ContextualBean.class); 211 assertEquals("NameA=1", bean.a.value); 212 assertEquals("NameB=2", bean.b.value); 213 214 // try again, to ensure caching etc works 215 bean = mapper.readValue("{\"a\":\"x\",\"b\":\"y\"}", ContextualBean.class); 216 assertEquals("NameA=x", bean.a.value); 217 assertEquals("NameB=y", bean.b.value); 218 } 219 testSimpleWithClassAnnotations()220 public void testSimpleWithClassAnnotations() throws Exception 221 { 222 ObjectMapper mapper = _mapperWithAnnotatedContextual(); 223 ContextualClassBean bean = mapper.readValue("{\"a\":\"1\",\"b\":\"2\"}", ContextualClassBean.class); 224 assertEquals("Class=1", bean.a.value); 225 assertEquals("NameB=2", bean.b.value); 226 // and again 227 bean = mapper.readValue("{\"a\":\"123\",\"b\":\"345\"}", ContextualClassBean.class); 228 assertEquals("Class=123", bean.a.value); 229 assertEquals("NameB=345", bean.b.value); 230 } 231 testAnnotatedCtor()232 public void testAnnotatedCtor() throws Exception 233 { 234 ObjectMapper mapper = _mapperWithAnnotatedContextual(); 235 ContextualCtorBean bean = mapper.readValue("{\"a\":\"foo\",\"b\":\"bar\"}", ContextualCtorBean.class); 236 assertEquals("CtorA=foo", bean.a); 237 assertEquals("CtorB=bar", bean.b); 238 239 bean = mapper.readValue("{\"a\":\"1\",\"b\":\"0\"}", ContextualCtorBean.class); 240 assertEquals("CtorA=1", bean.a); 241 assertEquals("CtorB=0", bean.b); 242 } 243 testAnnotatedArray()244 public void testAnnotatedArray() throws Exception 245 { 246 ObjectMapper mapper = _mapperWithAnnotatedContextual(); 247 ContextualArrayBean bean = mapper.readValue("{\"beans\":[\"x\"]}", ContextualArrayBean.class); 248 assertEquals(1, bean.beans.length); 249 assertEquals("array=x", bean.beans[0].value); 250 251 bean = mapper.readValue("{\"beans\":[\"a\",\"b\"]}", ContextualArrayBean.class); 252 assertEquals(2, bean.beans.length); 253 assertEquals("array=a", bean.beans[0].value); 254 assertEquals("array=b", bean.beans[1].value); 255 } 256 testAnnotatedList()257 public void testAnnotatedList() throws Exception 258 { 259 ObjectMapper mapper = _mapperWithAnnotatedContextual(); 260 ContextualListBean bean = mapper.readValue("{\"beans\":[\"x\"]}", ContextualListBean.class); 261 assertEquals(1, bean.beans.size()); 262 assertEquals("list=x", bean.beans.get(0).value); 263 264 bean = mapper.readValue("{\"beans\":[\"x\",\"y\",\"z\"]}", ContextualListBean.class); 265 assertEquals(3, bean.beans.size()); 266 assertEquals("list=x", bean.beans.get(0).value); 267 assertEquals("list=y", bean.beans.get(1).value); 268 assertEquals("list=z", bean.beans.get(2).value); 269 } 270 testAnnotatedMap()271 public void testAnnotatedMap() throws Exception 272 { 273 ObjectMapper mapper = _mapperWithAnnotatedContextual(); 274 ContextualMapBean bean = mapper.readValue("{\"beans\":{\"a\":\"b\"}}", ContextualMapBean.class); 275 assertEquals(1, bean.beans.size()); 276 Map.Entry<String,StringValue> entry = bean.beans.entrySet().iterator().next(); 277 assertEquals("a", entry.getKey()); 278 assertEquals("map=b", entry.getValue().value); 279 280 bean = mapper.readValue("{\"beans\":{\"x\":\"y\",\"1\":\"2\"}}", ContextualMapBean.class); 281 assertEquals(2, bean.beans.size()); 282 Iterator<Map.Entry<String,StringValue>> it = bean.beans.entrySet().iterator(); 283 entry = it.next(); 284 assertEquals("x", entry.getKey()); 285 assertEquals("map=y", entry.getValue().value); 286 entry = it.next(); 287 assertEquals("1", entry.getKey()); 288 assertEquals("map=2", entry.getValue().value); 289 } 290 291 // for [databind#165] testContextualType()292 public void testContextualType() throws Exception { 293 GenericBean bean = new ObjectMapper().readValue(aposToQuotes("{'stuff':{'1':'b'}}"), 294 GenericBean.class); 295 assertNotNull(bean.stuff); 296 assertEquals(1, bean.stuff.size()); 297 assertEquals("String", bean.stuff.get(Integer.valueOf(1))); 298 } 299 300 /* 301 /********************************************************** 302 /* Helper methods 303 /********************************************************** 304 */ 305 _mapperWithAnnotatedContextual()306 private ObjectMapper _mapperWithAnnotatedContextual() { 307 return ANNOTATED_CTXT_MAPPER; 308 } 309 } 310