• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.contextual;
2 
3 import java.io.IOException;
4 import java.lang.annotation.*;
5 import java.util.*;
6 
7 import com.fasterxml.jackson.annotation.*;
8 
9 import com.fasterxml.jackson.core.*;
10 import com.fasterxml.jackson.databind.*;
11 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
12 import com.fasterxml.jackson.databind.module.SimpleModule;
13 import com.fasterxml.jackson.databind.ser.ContextualSerializer;
14 import com.fasterxml.jackson.databind.ser.ResolvableSerializer;
15 
16 /**
17  * Test cases to verify that it is possible to define serializers
18  * that can use contextual information (like field/method
19  * annotations) for configuration.
20  */
21 public class TestContextualSerialization extends BaseMapTest
22 {
23     // NOTE: important; MUST be considered a 'Jackson' annotation to be seen
24     // (or recognized otherwise via AnnotationIntrospect.isHandled())
25     @Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
26     @Retention(RetentionPolicy.RUNTIME)
27     @JacksonAnnotation
28     public @interface Prefix {
value()29         public String value();
30     }
31 
32     static class ContextualBean
33     {
34         protected final String _value;
35 
ContextualBean(String s)36         public ContextualBean(String s) { _value = s; }
37 
38         @Prefix("see:")
getValue()39         public String getValue() { return _value; }
40     }
41 
42     // For [JACKSON-569]
43     static class AnnotatedContextualBean
44     {
45         @Prefix("prefix->")
46         @JsonSerialize(using=AnnotatedContextualSerializer.class)
47         protected final String value;
48 
AnnotatedContextualBean(String s)49         public AnnotatedContextualBean(String s) { value = s; }
50     }
51 
52 
53     @Prefix("wrappedBean:")
54     static class ContextualBeanWrapper
55     {
56         @Prefix("wrapped:")
57         public ContextualBean wrapped;
58 
ContextualBeanWrapper(String s)59         public ContextualBeanWrapper(String s) {
60             wrapped = new ContextualBean(s);
61         }
62     }
63 
64     static class ContextualArrayBean
65     {
66         @Prefix("array->")
67         public final String[] beans;
68 
ContextualArrayBean(String... strings)69         public ContextualArrayBean(String... strings) {
70             beans = strings;
71         }
72     }
73 
74     static class ContextualArrayElementBean
75     {
76         @Prefix("elem->")
77         @JsonSerialize(contentUsing=AnnotatedContextualSerializer.class)
78         public final String[] beans;
79 
ContextualArrayElementBean(String... strings)80         public ContextualArrayElementBean(String... strings) {
81             beans = strings;
82         }
83     }
84 
85     static class ContextualListBean
86     {
87         @Prefix("list->")
88         public final List<String> beans = new ArrayList<String>();
89 
ContextualListBean(String... strings)90         public ContextualListBean(String... strings) {
91             for (String string : strings) {
92                 beans.add(string);
93             }
94         }
95     }
96 
97     static class ContextualMapBean
98     {
99         @Prefix("map->")
100         public final Map<String, String> beans = new HashMap<String, String>();
101     }
102 
103     /**
104      * Another bean that has class annotations that should be visible for
105      * contextualizer, too
106      */
107     @Prefix("Voila->")
108     static class BeanWithClassConfig
109     {
110         public String value;
111 
BeanWithClassConfig(String v)112         public BeanWithClassConfig(String v) { value = v; }
113     }
114 
115     /**
116      * Annotation-based contextual serializer that simply prepends piece of text.
117      */
118     static class AnnotatedContextualSerializer
119         extends JsonSerializer<String>
120         implements ContextualSerializer
121     {
122         protected final String _prefix;
123 
AnnotatedContextualSerializer()124         public AnnotatedContextualSerializer() { this(""); }
AnnotatedContextualSerializer(String p)125         public AnnotatedContextualSerializer(String p) {
126             _prefix = p;
127         }
128 
129         @Override
serialize(String value, JsonGenerator jgen, SerializerProvider provider)130         public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException
131         {
132             jgen.writeString(_prefix + value);
133         }
134 
135         @Override
createContextual(SerializerProvider prov, BeanProperty property)136         public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
137                 throws JsonMappingException
138         {
139             String prefix = "UNKNOWN";
140             Prefix ann = null;
141             if (property != null) {
142                 ann = property.getAnnotation(Prefix.class);
143                 if (ann == null) {
144                     ann = property.getContextAnnotation(Prefix.class);
145                 }
146             }
147             if (ann != null) {
148                 prefix = ann.value();
149             }
150             return new AnnotatedContextualSerializer(prefix);
151         }
152     }
153 
154     static class ContextualAndResolvable
155         extends JsonSerializer<String>
156         implements ContextualSerializer, ResolvableSerializer
157     {
158         protected int isContextual;
159         protected int isResolved;
160 
ContextualAndResolvable()161         public ContextualAndResolvable() { this(0, 0); }
162 
ContextualAndResolvable(int resolved, int contextual)163         public ContextualAndResolvable(int resolved, int contextual)
164         {
165             isContextual = contextual;
166             isResolved = resolved;
167         }
168 
169         @Override
serialize(String value, JsonGenerator jgen, SerializerProvider provider)170         public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException
171         {
172             jgen.writeString("contextual="+isContextual+",resolved="+isResolved);
173         }
174 
175         @Override
createContextual(SerializerProvider prov, BeanProperty property)176         public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
177                 throws JsonMappingException
178         {
179             return new ContextualAndResolvable(isResolved, isContextual+1);
180         }
181 
182         @Override
resolve(SerializerProvider provider)183         public void resolve(SerializerProvider provider) {
184             ++isResolved;
185         }
186     }
187 
188     static class AccumulatingContextual
189         extends JsonSerializer<String>
190         implements ContextualSerializer
191     {
192         protected String desc;
193 
AccumulatingContextual()194         public AccumulatingContextual() { this(""); }
195 
AccumulatingContextual(String newDesc)196         public AccumulatingContextual(String newDesc) {
197             desc = newDesc;
198         }
199 
200         @Override
serialize(String value, JsonGenerator g, SerializerProvider provider)201         public void serialize(String value, JsonGenerator g, SerializerProvider provider) throws IOException
202         {
203             g.writeString(desc+"/"+value);
204         }
205 
206         @Override
createContextual(SerializerProvider prov, BeanProperty property)207         public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)
208                 throws JsonMappingException
209         {
210             if (property == null) {
211                 return new AccumulatingContextual(desc+"/ROOT");
212             }
213             return new AccumulatingContextual(desc+"/"+property.getName());
214         }
215     }
216 
217     /*
218     /**********************************************************
219     /* Unit tests
220     /**********************************************************
221      */
222 
223     // Test to verify that contextual serializer can make use of property
224     // (method, field) annotations.
testMethodAnnotations()225     public void testMethodAnnotations() throws Exception
226     {
227         ObjectMapper mapper = new ObjectMapper();
228         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
229         module.addSerializer(String.class, new AnnotatedContextualSerializer());
230         mapper.registerModule(module);
231         assertEquals("{\"value\":\"see:foobar\"}", mapper.writeValueAsString(new ContextualBean("foobar")));
232     }
233 
234     // Test to verify that contextual serializer can also use annotations
235     // for enclosing class.
testClassAnnotations()236     public void testClassAnnotations() throws Exception
237     {
238         ObjectMapper mapper = new ObjectMapper();
239         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
240         module.addSerializer(String.class, new AnnotatedContextualSerializer());
241         mapper.registerModule(module);
242         assertEquals("{\"value\":\"Voila->xyz\"}", mapper.writeValueAsString(new BeanWithClassConfig("xyz")));
243     }
244 
testWrappedBean()245     public void testWrappedBean() throws Exception
246     {
247         ObjectMapper mapper = new ObjectMapper();
248         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
249         module.addSerializer(String.class, new AnnotatedContextualSerializer());
250         mapper.registerModule(module);
251         assertEquals("{\"wrapped\":{\"value\":\"see:xyz\"}}", mapper.writeValueAsString(new ContextualBeanWrapper("xyz")));
252     }
253 
254     // Serializer should get passed property context even if contained in an array.
testMethodAnnotationInArray()255     public void testMethodAnnotationInArray() throws Exception
256     {
257         ObjectMapper mapper = new ObjectMapper();
258         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
259         module.addSerializer(String.class, new AnnotatedContextualSerializer());
260         mapper.registerModule(module);
261         ContextualArrayBean beans = new ContextualArrayBean("123");
262         assertEquals("{\"beans\":[\"array->123\"]}", mapper.writeValueAsString(beans));
263     }
264 
265     // Serializer should get passed property context even if contained in a Collection.
testMethodAnnotationInList()266     public void testMethodAnnotationInList() throws Exception
267     {
268         ObjectMapper mapper = new ObjectMapper();
269         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
270         module.addSerializer(String.class, new AnnotatedContextualSerializer());
271         mapper.registerModule(module);
272         ContextualListBean beans = new ContextualListBean("abc");
273         assertEquals("{\"beans\":[\"list->abc\"]}", mapper.writeValueAsString(beans));
274     }
275 
276     // Serializer should get passed property context even if contained in a Collection.
testMethodAnnotationInMap()277     public void testMethodAnnotationInMap() throws Exception
278     {
279         ObjectMapper mapper = new ObjectMapper();
280         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
281         module.addSerializer(String.class, new AnnotatedContextualSerializer());
282         mapper.registerModule(module);
283         ContextualMapBean map = new ContextualMapBean();
284         map.beans.put("first", "In Map");
285         assertEquals("{\"beans\":{\"first\":\"map->In Map\"}}", mapper.writeValueAsString(map));
286     }
287 
testContextualViaAnnotation()288     public void testContextualViaAnnotation() throws Exception
289     {
290         ObjectMapper mapper = new ObjectMapper();
291         AnnotatedContextualBean bean = new AnnotatedContextualBean("abc");
292         assertEquals("{\"value\":\"prefix->abc\"}", mapper.writeValueAsString(bean));
293     }
294 
testResolveOnContextual()295     public void testResolveOnContextual() throws Exception
296     {
297         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
298         module.addSerializer(String.class, new ContextualAndResolvable());
299         ObjectMapper mapper = jsonMapperBuilder()
300                 .addModule(module)
301                 .build();
302         assertEquals(quote("contextual=1,resolved=1"), mapper.writeValueAsString("abc"));
303 
304         // also: should NOT be called again
305         assertEquals(quote("contextual=1,resolved=1"), mapper.writeValueAsString("foo"));
306     }
307 
testContextualArrayElement()308     public void testContextualArrayElement() throws Exception
309     {
310         ObjectMapper mapper = newJsonMapper();
311         ContextualArrayElementBean beans = new ContextualArrayElementBean("456");
312         assertEquals("{\"beans\":[\"elem->456\"]}", mapper.writeValueAsString(beans));
313     }
314 
315     // Test to verify aspects of [databind#2429]
testRootContextualization2429()316     public void testRootContextualization2429() throws Exception
317     {
318         ObjectMapper mapper = jsonMapperBuilder()
319                 .addModule(new SimpleModule("test", Version.unknownVersion())
320                         .addSerializer(String.class, new AccumulatingContextual()))
321                 .build();
322         assertEquals(quote("/ROOT/foo"), mapper.writeValueAsString("foo"));
323         assertEquals(quote("/ROOT/bar"), mapper.writeValueAsString("bar"));
324         assertEquals(quote("/ROOT/3"), mapper.writeValueAsString("3"));
325     }
326 }
327