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