package com.fasterxml.jackson.databind.creators;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonValueInstantiator;
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* Test custom instantiators.
*/
public class TestValueInstantiator extends BaseMapTest
{
static class MyBean
{
String _secret;
public MyBean(String s, boolean bogus) {
_secret = s;
}
}
static class MysteryBean
{
Object value;
public MysteryBean(Object v) { value = v; }
}
static class CreatorBean
{
String _secret;
public String value;
protected CreatorBean(String s) {
_secret = s;
}
}
static abstract class InstantiatorBase extends ValueInstantiator.Base
{
public InstantiatorBase() {
super(Object.class);
}
@Override
public String getValueTypeDesc() {
return "UNKNOWN";
}
@Override
public boolean canCreateUsingDelegate() { return false; }
}
static abstract class PolymorphicBeanBase { }
static class PolymorphicBean extends PolymorphicBeanBase
{
public String name;
}
@SuppressWarnings("serial")
static class MyList extends ArrayList<Object>
{
public MyList(boolean b) { super(); }
}
@SuppressWarnings("serial")
static class MyMap extends HashMap<String,Object>
{
public MyMap(boolean b) { super(); }
public MyMap(String name) {
super();
put(name, name);
}
}
static class MyBeanInstantiator extends InstantiatorBase
{
@Override
public String getValueTypeDesc() {
return MyBean.class.getName();
}
@Override
public boolean canCreateUsingDefault() { return true; }
@Override
public MyBean createUsingDefault(DeserializationContext ctxt) {
return new MyBean("secret!", true);
}
}
/**
* Something more ambitious: semi-automated approach to polymorphic
* deserialization, using ValueInstantiator; from Object to any
* type...
*/
static class PolymorphicBeanInstantiator extends InstantiatorBase
{
@Override
public String getValueTypeDesc() {
return Object.class.getName();
}
@Override
public boolean canCreateFromObjectWith() { return true; }
@Override
public CreatorProperty[] getFromObjectArguments(DeserializationConfig config) {
return new CreatorProperty[] {
new CreatorProperty(new PropertyName("type"), config.constructType(Class.class), null,
null, null, null, 0, null,
PropertyMetadata.STD_REQUIRED)
};
}
@Override
public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) {
try {
Class<?> cls = (Class<?>) args[0];
return cls.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
static class CreatorMapInstantiator extends InstantiatorBase
{
@Override
public String getValueTypeDesc() {
return MyMap.class.getName();
}
@Override
public boolean canCreateFromObjectWith() { return true; }
@Override
public CreatorProperty[] getFromObjectArguments(DeserializationConfig config) {
return new CreatorProperty[] {
new CreatorProperty(new PropertyName("name"), config.constructType(String.class), null,
null, null, null, 0, null,
PropertyMetadata.STD_REQUIRED)
};
}
@Override
public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) {
return new MyMap((String) args[0]);
}
}
static class MyDelegateBeanInstantiator extends ValueInstantiator.Base
{
public MyDelegateBeanInstantiator() { super(Object.class); }
@Override
public String getValueTypeDesc() { return "xxx"; }
@Override
public boolean canCreateUsingDelegate() { return true; }
@Override
public JavaType getDelegateType(DeserializationConfig config) {
return config.constructType(Object.class);
}
@Override
public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) {
return new MyBean(""+delegate, true);
}
}
static class MyListInstantiator extends InstantiatorBase
{
@Override
public String getValueTypeDesc() {
return MyList.class.getName();
}
@Override
public boolean canCreateUsingDefault() { return true; }
@Override
public MyList createUsingDefault(DeserializationContext ctxt) {
return new MyList(true);
}
}
static class MyDelegateListInstantiator extends ValueInstantiator.Base
{
public MyDelegateListInstantiator() { super(Object.class); }
@Override
public String getValueTypeDesc() { return "xxx"; }
@Override
public boolean canCreateUsingDelegate() { return true; }
@Override
public JavaType getDelegateType(DeserializationConfig config) {
return config.constructType(Object.class);
}
@Override
public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) {
MyList list = new MyList(true);
list.add(delegate);
return list;
}
}
static class MyMapInstantiator extends InstantiatorBase
{
@Override
public String getValueTypeDesc() {
return MyMap.class.getName();
}
@Override
public boolean canCreateUsingDefault() { return true; }
@Override
public MyMap createUsingDefault(DeserializationContext ctxt) {
return new MyMap(true);
}
}
static class MyDelegateMapInstantiator extends ValueInstantiator.Base
{
public MyDelegateMapInstantiator() { super(Object.class); }
@Override
public String getValueTypeDesc() { return "xxx"; }
@Override
public boolean canCreateUsingDelegate() { return true; }
@Override
public JavaType getDelegateType(DeserializationConfig config) {
return TypeFactory.defaultInstance().constructType(Object.class);
}
@Override
public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) {
MyMap map = new MyMap(true);
map.put("value", delegate);
return map;
}
}
@JsonValueInstantiator(AnnotatedBeanInstantiator.class)
static class AnnotatedBean {
protected final String a;
protected final int b;
public AnnotatedBean(String a, int b) {
this.a = a;
this.b = b;
}
}
static class AnnotatedBeanInstantiator extends InstantiatorBase
{
@Override
public String getValueTypeDesc() {
return AnnotatedBean.class.getName();
}
@Override
public boolean canCreateUsingDefault() { return true; }
@Override
public AnnotatedBean createUsingDefault(DeserializationContext ctxt) {
return new AnnotatedBean("foo", 3);
}
}
@SuppressWarnings("serial")
static class MyModule extends SimpleModule
{
public MyModule(Class<?> cls, ValueInstantiator inst)
{
super("Test", Version.unknownVersion());
this.addValueInstantiator(cls, inst);
}
}
@JsonValueInstantiator(AnnotatedBeanDelegatingInstantiator.class)
static class AnnotatedBeanDelegating {
protected final Object value;
public AnnotatedBeanDelegating(Object v, boolean bogus) {
value = v;
}
}
static class AnnotatedBeanDelegatingInstantiator extends InstantiatorBase
{
@Override
public String getValueTypeDesc() {
return AnnotatedBeanDelegating.class.getName();
}
@Override
public boolean canCreateUsingDelegate() { return true; }
@Override
public JavaType getDelegateType(DeserializationConfig config) {
return config.constructType(Map.class);
}
@Override
public AnnotatedWithParams getDelegateCreator() {
return null;
}
@Override
public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) throws IOException {
return new AnnotatedBeanDelegating(delegate, false);
}
}
/*
/**********************************************************
/* Unit tests for default creators
/**********************************************************
*/
private final ObjectMapper MAPPER = objectMapper();
public void testCustomBeanInstantiator() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MyBean.class, new MyBeanInstantiator()));
MyBean bean = mapper.readValue("{}", MyBean.class);
assertNotNull(bean);
assertEquals("secret!", bean._secret);
}
public void testCustomListInstantiator() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MyList.class, new MyListInstantiator()));
MyList result = mapper.readValue("[]", MyList.class);
assertNotNull(result);
assertEquals(MyList.class, result.getClass());
assertEquals(0, result.size());
}
public void testCustomMapInstantiator() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MyMap.class, new MyMapInstantiator()));
MyMap result = mapper.readValue("{ \"a\":\"b\" }", MyMap.class);
assertNotNull(result);
assertEquals(MyMap.class, result.getClass());
assertEquals(1, result.size());
}
/*
/**********************************************************
/* Unit tests for delegate creators
/**********************************************************
*/
public void testDelegateBeanInstantiator() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MyBean.class, new MyDelegateBeanInstantiator()));
MyBean bean = mapper.readValue("123", MyBean.class);
assertNotNull(bean);
assertEquals("123", bean._secret);
}
public void testDelegateListInstantiator() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MyList.class, new MyDelegateListInstantiator()));
MyList result = mapper.readValue("123", MyList.class);
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(Integer.valueOf(123), result.get(0));
}
public void testDelegateMapInstantiator() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MyMap.class, new MyDelegateMapInstantiator()));
MyMap result = mapper.readValue("123", MyMap.class);
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(Integer.valueOf(123), result.values().iterator().next());
}
public void testCustomDelegateInstantiator() throws Exception
{
AnnotatedBeanDelegating value = MAPPER.readValue("{\"a\":3}", AnnotatedBeanDelegating.class);
assertNotNull(value);
Object ob = value.value;
assertNotNull(ob);
assertTrue(ob instanceof Map);
}
/*
/**********************************************************
/* Unit tests for property-based creators
/**********************************************************
*/
public void testPropertyBasedBeanInstantiator() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(CreatorBean.class,
new InstantiatorBase() {
@Override
public boolean canCreateFromObjectWith() { return true; }
@Override
public CreatorProperty[] getFromObjectArguments(DeserializationConfig config) {
return new CreatorProperty[] {
new CreatorProperty(new PropertyName("secret"), config.constructType(String.class), null,
null, null, null, 0, null,
PropertyMetadata.STD_REQUIRED)
};
}
@Override
public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) {
return new CreatorBean((String) args[0]);
}
}));
CreatorBean bean = mapper.readValue("{\"secret\":123,\"value\":37}", CreatorBean.class);
assertNotNull(bean);
assertEquals("123", bean._secret);
}
public void testPropertyBasedMapInstantiator() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MyMap.class, new CreatorMapInstantiator()));
MyMap result = mapper.readValue("{\"name\":\"bob\", \"x\":\"y\"}", MyMap.class);
assertNotNull(result);
assertEquals(2, result.size());
assertEquals("bob", result.get("bob"));
assertEquals("y", result.get("x"));
}
/*
/**********************************************************
/* Unit tests for scalar-delegates
/**********************************************************
*/
public void testBeanFromString() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MysteryBean.class,
new InstantiatorBase() {
@Override
public boolean canCreateFromString() { return true; }
@Override
public Object createFromString(DeserializationContext ctxt, String value) {
return new MysteryBean(value);
}
}));
MysteryBean result = mapper.readValue(quote("abc"), MysteryBean.class);
assertNotNull(result);
assertEquals("abc", result.value);
}
public void testBeanFromInt() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MysteryBean.class,
new InstantiatorBase() {
@Override
public boolean canCreateFromInt() { return true; }
@Override
public Object createFromInt(DeserializationContext ctxt, int value) {
return new MysteryBean(value+1);
}
}));
MysteryBean result = mapper.readValue("37", MysteryBean.class);
assertNotNull(result);
assertEquals(Integer.valueOf(38), result.value);
}
public void testBeanFromLong() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MysteryBean.class,
new InstantiatorBase() {
@Override
public boolean canCreateFromLong() { return true; }
@Override
public Object createFromLong(DeserializationContext ctxt, long value) {
return new MysteryBean(value+1L);
}
}));
MysteryBean result = mapper.readValue("9876543210", MysteryBean.class);
assertNotNull(result);
assertEquals(Long.valueOf(9876543211L), result.value);
}
public void testBeanFromDouble() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MysteryBean.class,
new InstantiatorBase() {
@Override
public boolean canCreateFromDouble() { return true; }
@Override
public Object createFromDouble(DeserializationContext ctxt, double value) {
return new MysteryBean(2.0 * value);
}
}));
MysteryBean result = mapper.readValue("0.25", MysteryBean.class);
assertNotNull(result);
assertEquals(Double.valueOf(0.5), result.value);
}
public void testBeanFromBoolean() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(MysteryBean.class,
new InstantiatorBase() {
@Override
public boolean canCreateFromBoolean() { return true; }
@Override
public Object createFromBoolean(DeserializationContext ctxt, boolean value) {
return new MysteryBean(Boolean.valueOf(value));
}
}));
MysteryBean result = mapper.readValue("true", MysteryBean.class);
assertNotNull(result);
assertEquals(Boolean.TRUE, result.value);
}
/*
/**********************************************************
/* Other tests
/**********************************************************
*/
/**
* Beyond basic features, it should be possible to even implement
* polymorphic handling...
*/
public void testPolymorphicCreatorBean() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MyModule(PolymorphicBeanBase.class, new PolymorphicBeanInstantiator()));
String JSON = "{\"type\":"+quote(PolymorphicBean.class.getName())+",\"name\":\"Axel\"}";
PolymorphicBeanBase result = mapper.readValue(JSON, PolymorphicBeanBase.class);
assertNotNull(result);
assertSame(PolymorphicBean.class, result.getClass());
assertEquals("Axel", ((PolymorphicBean) result).name);
}
public void testEmptyBean() throws Exception
{
AnnotatedBean bean = MAPPER.readValue("{}", AnnotatedBean.class);
assertNotNull(bean);
assertEquals("foo", bean.a);
assertEquals(3, bean.b);
}
// @since 2.8
public void testErrorMessageForMissingCtor() throws Exception
{
// first fail, check message from JSON Object (no default ctor)
try {
MAPPER.readValue("{ }", MyBean.class);
fail("Should not succeed");
} catch (JsonMappingException e) {
verifyException(e, "Can not construct instance of");
verifyException(e, "no Creators");
// as per [databind#1414], is definition problem
assertEquals(InvalidDefinitionException.class, e.getClass());
}
}
// @since 2.8
public void testErrorMessageForMissingStringCtor() throws Exception
{
// then from JSON String
try {
MAPPER.readValue("\"foo\"", MyBean.class);
fail("Should not succeed");
} catch (JsonMappingException e) {
verifyException(e, "Can not construct instance of");
verifyException(e, "no String-argument constructor/factory");
// as per [databind#1414], is definition problem
assertEquals(InvalidDefinitionException.class, e.getClass());
}
}
}