package com.fasterxml.jackson.databind.module;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
@SuppressWarnings("serial")
public class SimpleModuleTest extends BaseMapTest
{
/**
* Trivial bean that requires custom serializer and deserializer
*/
final static class CustomBean
{
protected String str;
protected int num;
public CustomBean(String s, int i) {
str = s;
num = i;
}
}
static enum SimpleEnum { A, B; }
// Extend SerializerBase to get access to declared handledType
static class CustomBeanSerializer extends StdSerializer<CustomBean>
{
public CustomBeanSerializer() { super(CustomBean.class); }
@Override
public void serialize(CustomBean value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException
{
// We will write it as a String, with '|' as delimiter
jgen.writeString(value.str + "|" + value.num);
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
return null;
}
}
static class CustomBeanDeserializer extends JsonDeserializer<CustomBean>
{
@Override
public CustomBean deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
String text = jp.getText();
int ix = text.indexOf('|');
if (ix < 0) {
throw new IOException("Failed to parse String value of \""+text+"\"");
}
String str = text.substring(0, ix);
int num = Integer.parseInt(text.substring(ix+1));
return new CustomBean(str, num);
}
}
static class SimpleEnumSerializer extends StdSerializer<SimpleEnum>
{
public SimpleEnumSerializer() { super(SimpleEnum.class); }
@Override
public void serialize(SimpleEnum value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException
{
jgen.writeString(value.name().toLowerCase());
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
return null;
}
}
static class SimpleEnumDeserializer extends JsonDeserializer<SimpleEnum>
{
@Override
public SimpleEnum deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
return SimpleEnum.valueOf(jp.getText().toUpperCase());
}
}
interface Base {
public String getText();
}
static class Impl1 implements Base {
@Override
public String getText() { return "1"; }
}
static class Impl2 extends Impl1 {
@Override
public String getText() { return "2"; }
}
static class BaseSerializer extends StdScalarSerializer<Base>
{
public BaseSerializer() { super(Base.class); }
@Override
public void serialize(Base value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString("Base:"+value.getText());
}
}
static class MixableBean {
public int a = 1;
public int b = 2;
public int c = 3;
}
@JsonPropertyOrder({"c", "a", "b"})
static class MixInForOrder { }
protected static class MySimpleSerializers extends SimpleSerializers { }
protected static class MySimpleDeserializers extends SimpleDeserializers { }
/**
* Test module which uses custom 'serializers' and 'deserializers' container; used
* to trigger type problems.
*/
protected static class MySimpleModule extends SimpleModule
{
public MySimpleModule(String name, Version version) {
super(name, version);
_deserializers = new MySimpleDeserializers();
_serializers = new MySimpleSerializers();
}
}
protected static class ContextVerifierModule extends Module
{
@Override
public String getModuleName() { return "x"; }
@Override
public Version version() { return Version.unknownVersion(); }
@Override
public void setupModule(SetupContext context)
{
ObjectCodec c = context.getOwner();
assertNotNull(c);
assertTrue(c instanceof ObjectMapper);
ObjectMapper m = context.getOwner();
assertNotNull(m);
}
}
static class TestModule626 extends SimpleModule {
final Class<?> mixin, target;
public TestModule626(Class<?> t, Class<?> m) {
super("Test");
target = t;
mixin = m;
}
@Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(target, mixin);
}
}
/*
/**********************************************************
/* Unit tests; first, verifying need for custom handlers
/**********************************************************
*/
/**
* Basic test to ensure we do not have functioning default
* serializers for custom types used in tests.
*/
public void testWithoutModule()
{
ObjectMapper mapper = new ObjectMapper();
// first: serialization failure:
try {
mapper.writeValueAsString(new CustomBean("foo", 3));
fail("Should have caused an exception");
} catch (IOException e) {
verifyException(e, "No serializer found");
}
// then deserialization
try {
mapper.readValue("{\"str\":\"ab\",\"num\":2}", CustomBean.class);
fail("Should have caused an exception");
} catch (IOException e) {
verifyException(e, "Can not construct");
verifyException(e, "no creators");
}
}
/*
/**********************************************************
/* Unit tests; simple serializers
/**********************************************************
*/
public void testSimpleBeanSerializer() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
mod.addSerializer(new CustomBeanSerializer());
mapper.registerModule(mod);
assertEquals(quote("abcde|5"), mapper.writeValueAsString(new CustomBean("abcde", 5)));
}
public void testSimpleEnumSerializer() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
mod.addSerializer(new SimpleEnumSerializer());
// for fun, call "multi-module" registration
mapper.registerModules(mod);
assertEquals(quote("b"), mapper.writeValueAsString(SimpleEnum.B));
}
public void testSimpleInterfaceSerializer() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
mod.addSerializer(new BaseSerializer());
// and another variant here too
List<Module> mods = Arrays.asList((Module) mod);
mapper.registerModules(mods);
assertEquals(quote("Base:1"), mapper.writeValueAsString(new Impl1()));
assertEquals(quote("Base:2"), mapper.writeValueAsString(new Impl2()));
}
/*
/**********************************************************
/* Unit tests; simple deserializers
/**********************************************************
*/
public void testSimpleBeanDeserializer() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
mod.addDeserializer(CustomBean.class, new CustomBeanDeserializer());
mapper.registerModule(mod);
CustomBean bean = mapper.readValue(quote("xyz|3"), CustomBean.class);
assertEquals("xyz", bean.str);
assertEquals(3, bean.num);
}
public void testSimpleEnumDeserializer() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule mod = new SimpleModule("test", Version.unknownVersion());
mod.addDeserializer(SimpleEnum.class, new SimpleEnumDeserializer());
mapper.registerModule(mod);
SimpleEnum result = mapper.readValue(quote("a"), SimpleEnum.class);
assertSame(SimpleEnum.A, result);
}
public void testMultipleModules() throws Exception
{
MySimpleModule mod1 = new MySimpleModule("test1", Version.unknownVersion());
SimpleModule mod2 = new SimpleModule("test2", Version.unknownVersion());
mod1.addSerializer(SimpleEnum.class, new SimpleEnumSerializer());
mod1.addDeserializer(CustomBean.class, new CustomBeanDeserializer());
Map<Class<?>,JsonDeserializer<?>> desers = new HashMap<>();
desers.put(SimpleEnum.class, new SimpleEnumDeserializer());
mod2.setDeserializers(new SimpleDeserializers(desers));
mod2.addSerializer(CustomBean.class, new CustomBeanSerializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(mod1);
mapper.registerModule(mod2);
assertEquals(quote("b"), mapper.writeValueAsString(SimpleEnum.B));
SimpleEnum result = mapper.readValue(quote("a"), SimpleEnum.class);
assertSame(SimpleEnum.A, result);
// also let's try it with different order of registration, just in case
mapper = new ObjectMapper();
mapper.registerModule(mod2);
mapper.registerModule(mod1);
assertEquals(quote("b"), mapper.writeValueAsString(SimpleEnum.B));
result = mapper.readValue(quote("a"), SimpleEnum.class);
assertSame(SimpleEnum.A, result);
}
/*
/**********************************************************
/* Unit tests; other
/**********************************************************
*/
public void testMixIns() throws Exception
{
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.setMixInAnnotation(MixableBean.class, MixInForOrder.class);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Map<String,Object> props = this.writeAndMap(mapper, new MixableBean());
assertEquals(3, props.size());
assertEquals(Integer.valueOf(3), props.get("c"));
assertEquals(Integer.valueOf(1), props.get("a"));
assertEquals(Integer.valueOf(2), props.get("b"));
}
public void testAccessToMapper() throws Exception
{
ContextVerifierModule module = new ContextVerifierModule();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
}
// [databind#626]
public void testMixIns626() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// no real annotations, but nominally add ones from 'String' to 'Object', just for testing
mapper.registerModule(new TestModule626(Object.class, String.class));
Class<?> found = mapper.findMixInClassFor(Object.class);
assertEquals(String.class, found);
}
public void testAutoDiscovery() throws Exception
{
List<Module> mods = ObjectMapper.findModules();
assertEquals(0, mods.size());
}
}