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