• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.module;
2 
3 import java.io.IOException;
4 import java.lang.reflect.Type;
5 import java.util.*;
6 
7 
8 import com.fasterxml.jackson.core.*;
9 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
10 import com.fasterxml.jackson.databind.*;
11 import com.fasterxml.jackson.databind.module.SimpleDeserializers;
12 import com.fasterxml.jackson.databind.module.SimpleModule;
13 import com.fasterxml.jackson.databind.module.SimpleSerializers;
14 
15 import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
16 import com.fasterxml.jackson.databind.ser.std.StdSerializer;
17 
18 @SuppressWarnings("serial")
19 public class SimpleModuleTest extends BaseMapTest
20 {
21     /**
22      * Trivial bean that requires custom serializer and deserializer
23      */
24     final static class CustomBean
25     {
26         protected String str;
27         protected int num;
28 
CustomBean(String s, int i)29         public CustomBean(String s, int i) {
30             str = s;
31             num = i;
32         }
33     }
34 
35     static enum SimpleEnum { A, B; }
36 
37     // Extend SerializerBase to get access to declared handledType
38     static class CustomBeanSerializer extends StdSerializer<CustomBean>
39     {
CustomBeanSerializer()40         public CustomBeanSerializer() { super(CustomBean.class); }
41 
42         @Override
serialize(CustomBean value, JsonGenerator jgen, SerializerProvider provider)43         public void serialize(CustomBean value, JsonGenerator jgen, SerializerProvider provider)
44             throws IOException, JsonProcessingException
45         {
46             // We will write it as a String, with '|' as delimiter
47             jgen.writeString(value.str + "|" + value.num);
48         }
49 
50         @Override
getSchema(SerializerProvider provider, Type typeHint)51         public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
52             return null;
53         }
54     }
55 
56     static class CustomBeanDeserializer extends JsonDeserializer<CustomBean>
57     {
58         @Override
deserialize(JsonParser jp, DeserializationContext ctxt)59         public CustomBean deserialize(JsonParser jp, DeserializationContext ctxt)
60             throws IOException, JsonProcessingException
61         {
62             String text = jp.getText();
63             int ix = text.indexOf('|');
64             if (ix < 0) {
65                 throw new IOException("Failed to parse String value of \""+text+"\"");
66             }
67             String str = text.substring(0, ix);
68             int num = Integer.parseInt(text.substring(ix+1));
69             return new CustomBean(str, num);
70         }
71     }
72 
73     static class SimpleEnumSerializer extends StdSerializer<SimpleEnum>
74     {
SimpleEnumSerializer()75         public SimpleEnumSerializer() { super(SimpleEnum.class); }
76 
77         @Override
serialize(SimpleEnum value, JsonGenerator jgen, SerializerProvider provider)78         public void serialize(SimpleEnum value, JsonGenerator jgen, SerializerProvider provider)
79             throws IOException, JsonProcessingException
80         {
81             jgen.writeString(value.name().toLowerCase());
82         }
83 
84         @Override
getSchema(SerializerProvider provider, Type typeHint)85         public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
86             return null;
87         }
88     }
89 
90     static class SimpleEnumDeserializer extends JsonDeserializer<SimpleEnum>
91     {
92         @Override
deserialize(JsonParser jp, DeserializationContext ctxt)93         public SimpleEnum deserialize(JsonParser jp, DeserializationContext ctxt)
94             throws IOException, JsonProcessingException
95         {
96             return SimpleEnum.valueOf(jp.getText().toUpperCase());
97         }
98     }
99 
100     interface Base {
getText()101         public String getText();
102     }
103 
104     static class Impl1 implements Base {
105         @Override
getText()106         public String getText() { return "1"; }
107     }
108 
109     static class Impl2 extends Impl1 {
110         @Override
getText()111         public String getText() { return "2"; }
112     }
113 
114     static class BaseSerializer extends StdScalarSerializer<Base>
115     {
BaseSerializer()116         public BaseSerializer() { super(Base.class); }
117 
118         @Override
serialize(Base value, JsonGenerator jgen, SerializerProvider provider)119         public void serialize(Base value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
120             jgen.writeString("Base:"+value.getText());
121         }
122     }
123 
124     static class MixableBean {
125         public int a = 1;
126         public int b = 2;
127         public int c = 3;
128     }
129 
130     @JsonPropertyOrder({"c", "a", "b"})
131     static class MixInForOrder { }
132 
133     protected static class MySimpleSerializers extends SimpleSerializers { }
134     protected static class MySimpleDeserializers extends SimpleDeserializers { }
135 
136     /**
137      * Test module which uses custom 'serializers' and 'deserializers' container; used
138      * to trigger type problems.
139      */
140     protected static class MySimpleModule extends SimpleModule
141     {
MySimpleModule(String name, Version version)142         public MySimpleModule(String name, Version version) {
143             super(name, version);
144             _deserializers = new MySimpleDeserializers();
145             _serializers = new MySimpleSerializers();
146         }
147     }
148 
149     /**
150      * Test module that is different from MySimpleModule. Used to test registration
151      * of multiple modules.
152      */
153     protected static class AnotherSimpleModule extends SimpleModule
154     {
AnotherSimpleModule(String name, Version version)155         public AnotherSimpleModule(String name, Version version) {
156             super(name, version);
157         }
158     }
159 
160     protected static class ContextVerifierModule extends com.fasterxml.jackson.databind.Module
161     {
162         @Override
getModuleName()163         public String getModuleName() { return "x"; }
164 
165         @Override
version()166         public Version version() { return Version.unknownVersion(); }
167 
168         @Override
setupModule(SetupContext context)169         public void setupModule(SetupContext context)
170         {
171             ObjectCodec c = context.getOwner();
172             assertNotNull(c);
173             assertTrue(c instanceof ObjectMapper);
174             ObjectMapper m = context.getOwner();
175             assertNotNull(m);
176         }
177     }
178 
179     static class TestModule626 extends SimpleModule {
180         final Class<?> mixin, target;
TestModule626(Class<?> t, Class<?> m)181         public TestModule626(Class<?> t, Class<?> m) {
182             super("Test");
183             target = t;
184             mixin = m;
185         }
186 
187         @Override
setupModule(SetupContext context)188         public void setupModule(SetupContext context) {
189             context.setMixInAnnotations(target, mixin);
190         }
191     }
192 
193     /*
194     /**********************************************************
195     /* Unit tests; first, verifying need for custom handlers
196     /**********************************************************
197      */
198 
199     /**
200      * Basic test to ensure we do not have functioning default
201      * serializers for custom types used in tests.
202      */
testWithoutModule()203     public void testWithoutModule()
204     {
205         ObjectMapper mapper = new ObjectMapper();
206         // first: serialization failure:
207         try {
208             mapper.writeValueAsString(new CustomBean("foo", 3));
209             fail("Should have caused an exception");
210         } catch (IOException e) {
211             verifyException(e, "No serializer found");
212         }
213 
214         // then deserialization
215         try {
216             mapper.readValue("{\"str\":\"ab\",\"num\":2}", CustomBean.class);
217             fail("Should have caused an exception");
218         } catch (IOException e) {
219             verifyException(e, "Cannot construct");
220             verifyException(e, "no creators");
221         }
222     }
223 
224     /*
225     /**********************************************************
226     /* Unit tests; simple serializers
227     /**********************************************************
228      */
229 
testSimpleBeanSerializer()230     public void testSimpleBeanSerializer() throws Exception
231     {
232         ObjectMapper mapper = new ObjectMapper();
233         SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
234         mod.addSerializer(new CustomBeanSerializer());
235         mapper.registerModule(mod);
236         assertEquals(quote("abcde|5"), mapper.writeValueAsString(new CustomBean("abcde", 5)));
237     }
238 
testSimpleEnumSerializer()239     public void testSimpleEnumSerializer() throws Exception
240     {
241         ObjectMapper mapper = new ObjectMapper();
242         SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
243         mod.addSerializer(new SimpleEnumSerializer());
244         // for fun, call "multi-module" registration
245         mapper.registerModules(mod);
246         assertEquals(quote("b"), mapper.writeValueAsString(SimpleEnum.B));
247     }
248 
testSimpleInterfaceSerializer()249     public void testSimpleInterfaceSerializer() throws Exception
250     {
251         ObjectMapper mapper = new ObjectMapper();
252         SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
253         mod.addSerializer(new BaseSerializer());
254         // and another variant here too
255         List<SimpleModule> mods = Arrays.asList(mod);
256         mapper.registerModules(mods);
257         assertEquals(quote("Base:1"), mapper.writeValueAsString(new Impl1()));
258         assertEquals(quote("Base:2"), mapper.writeValueAsString(new Impl2()));
259     }
260 
261     /*
262     /**********************************************************
263     /* Unit tests; simple deserializers
264     /**********************************************************
265      */
266 
testSimpleBeanDeserializer()267     public void testSimpleBeanDeserializer() throws Exception
268     {
269         ObjectMapper mapper = new ObjectMapper();
270         SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
271         mod.addDeserializer(CustomBean.class, new CustomBeanDeserializer());
272         mapper.registerModule(mod);
273         CustomBean bean = mapper.readValue(quote("xyz|3"), CustomBean.class);
274         assertEquals("xyz", bean.str);
275         assertEquals(3, bean.num);
276     }
277 
testSimpleEnumDeserializer()278     public void testSimpleEnumDeserializer() throws Exception
279     {
280         ObjectMapper mapper = new ObjectMapper();
281         SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
282         mod.addDeserializer(SimpleEnum.class, new SimpleEnumDeserializer());
283         mapper.registerModule(mod);
284         SimpleEnum result = mapper.readValue(quote("a"), SimpleEnum.class);
285         assertSame(SimpleEnum.A, result);
286     }
287 
testMultipleModules()288     public void testMultipleModules() throws Exception
289     {
290         MySimpleModule mod1 = new MySimpleModule("test1", Version.unknownVersion());
291         SimpleModule mod2 = new SimpleModule("test2", Version.unknownVersion());
292         mod1.addSerializer(SimpleEnum.class, new SimpleEnumSerializer());
293         mod1.addDeserializer(CustomBean.class, new CustomBeanDeserializer());
294 
295         Map<Class<?>,JsonDeserializer<?>> desers = new HashMap<>();
296         desers.put(SimpleEnum.class, new SimpleEnumDeserializer());
297         mod2.setDeserializers(new SimpleDeserializers(desers));
298         mod2.addSerializer(CustomBean.class, new CustomBeanSerializer());
299 
300         ObjectMapper mapper = new ObjectMapper();
301         mapper.registerModule(mod1);
302         mapper.registerModule(mod2);
303         assertEquals(quote("b"), mapper.writeValueAsString(SimpleEnum.B));
304         SimpleEnum result = mapper.readValue(quote("a"), SimpleEnum.class);
305         assertSame(SimpleEnum.A, result);
306 
307         // also let's try it with different order of registration, just in case
308         mapper = new ObjectMapper();
309         mapper.registerModule(mod2);
310         mapper.registerModule(mod1);
311         assertEquals(quote("b"), mapper.writeValueAsString(SimpleEnum.B));
312         result = mapper.readValue(quote("a"), SimpleEnum.class);
313         assertSame(SimpleEnum.A, result);
314     }
315 
testGetRegisteredModules()316     public void testGetRegisteredModules()
317     {
318         MySimpleModule mod1 = new MySimpleModule("test1", Version.unknownVersion());
319         AnotherSimpleModule mod2 = new AnotherSimpleModule("test2", Version.unknownVersion());
320 
321         ObjectMapper mapper = new ObjectMapper();
322 
323         mapper.registerModule(mod1);
324         mapper.registerModule(mod2);
325 
326         Set<Object> registeredModuleIds = mapper.getRegisteredModuleIds();
327         assertEquals(2, registeredModuleIds.size());
328         assertTrue(registeredModuleIds.contains(mod1.getTypeId()));
329         assertTrue(registeredModuleIds.contains(mod2.getTypeId()));
330 
331         // 01-Jul-2019, [databind#2374]: verify empty list is fine
332         mapper = new ObjectMapper();
333         assertEquals(0, mapper.getRegisteredModuleIds().size());
334     }
335 
336     /*
337     /**********************************************************
338     /* Unit tests; other
339     /**********************************************************
340      */
341 
testMixIns()342     public void testMixIns() throws Exception
343     {
344         SimpleModule module = new SimpleModule("test", Version.unknownVersion());
345         module.setMixInAnnotation(MixableBean.class, MixInForOrder.class);
346         ObjectMapper mapper = new ObjectMapper();
347         mapper.registerModule(module);
348         Map<String,Object> props = this.writeAndMap(mapper, new MixableBean());
349         assertEquals(3, props.size());
350         assertEquals(Integer.valueOf(3), props.get("c"));
351         assertEquals(Integer.valueOf(1), props.get("a"));
352         assertEquals(Integer.valueOf(2), props.get("b"));
353     }
354 
testAccessToMapper()355     public void testAccessToMapper() throws Exception
356     {
357         ContextVerifierModule module = new ContextVerifierModule();
358         ObjectMapper mapper = new ObjectMapper();
359         mapper.registerModule(module);
360     }
361 
362     // [databind#626]
testMixIns626()363     public void testMixIns626() throws Exception
364     {
365         ObjectMapper mapper = new ObjectMapper();
366         // no real annotations, but nominally add ones from 'String' to 'Object', just for testing
367         mapper.registerModule(new TestModule626(Object.class, String.class));
368         Class<?> found = mapper.findMixInClassFor(Object.class);
369         assertEquals(String.class, found);
370     }
371 
testAutoDiscovery()372     public void testAutoDiscovery() throws Exception
373     {
374         List<?> mods = ObjectMapper.findModules();
375         assertEquals(0, mods.size());
376     }
377 }
378