blob: d97526d612a62ab0642372e8867ae916fef2411b [file] [log] [blame]
package org.codehaus.jackson.map.jsontype;
import java.util.*;
import org.codehaus.jackson.*;
import org.codehaus.jackson.annotate.JsonTypeInfo;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.util.TokenBuffer;
public class TestDefaultForObject
extends BaseMapTest
{
/*
/**********************************************************
/* Helper types
/**********************************************************
*/
static abstract class AbstractBean { }
static class StringBean extends AbstractBean { // ha, punny!
public String name;
public StringBean() { this(null); }
protected StringBean(String n) { name = n; }
}
enum Choice { YES, NO; }
/**
* Another enum type, but this time forcing sub-classing
*/
enum ComplexChoice {
MAYBE(true), PROBABLY_NOT(false);
private boolean state;
private ComplexChoice(boolean b) { state = b; }
@Override
public String toString() { return String.valueOf(state); }
}
// [JACKSON-311]
static class PolymorphicType {
public String foo;
public Object bar;
public PolymorphicType() { }
public PolymorphicType(String foo, int bar) {
this.foo = foo;
this.bar = bar;
}
}
final static class BeanHolder
{
public AbstractBean bean;
public BeanHolder() { }
public BeanHolder(AbstractBean b) { bean = b; }
}
final static class ObjectHolder
{
public Object value;
public ObjectHolder() { }
public ObjectHolder(Object v) { value = v; }
}
// [JACKSON-352]
static class DomainBean {
public int weight;
}
static class DiscussBean extends DomainBean {
public String subject;
}
static public class DomainBeanWrapper {
public String name;
public Object myBean;
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
/**
* Unit test that verifies that a bean is stored with type information,
* when declared type is <code>Object.class</code> (since it is within
* Object[]), and default type information is enabled.
*/
public void testBeanAsObject() throws Exception
{
ObjectMapper m = new ObjectMapper();
m.enableDefaultTyping();
// note: need to wrap, to get declared as Object
String str = m.writeValueAsString(new Object[] { new StringBean("abc") });
_verifySerializationAsMap(str);
// Ok: serialization seems to work as expected. Now deserialize:
Object ob = m.readValue(str, Object[].class);
assertNotNull(ob);
Object[] result = (Object[]) ob;
assertNotNull(result[0]);
assertEquals(StringBean.class, result[0].getClass());
assertEquals("abc", ((StringBean) result[0]).name);
}
/**
* Unit test that verifies that an abstract bean is stored with type information
* if default type information is enabled for non-concrete types.
*/
public void testAbstractBean() throws Exception
{
// First, let's verify that we'd fail without enabling default type info
ObjectMapper m = new ObjectMapper();
AbstractBean[] input = new AbstractBean[] { new StringBean("xyz") };
String serial = m.writeValueAsString(input);
try {
m.readValue(serial, AbstractBean[].class);
fail("Should have failed");
} catch (JsonMappingException e) {
// let's use whatever is currently thrown exception... may change tho
verifyException(e, "can not construct");
}
// and then that we will succeed with default type info
m = new ObjectMapper();
m.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
serial = m.writeValueAsString(input);
AbstractBean[] beans = m.readValue(serial, AbstractBean[].class);
assertEquals(1, beans.length);
assertEquals(StringBean.class, beans[0].getClass());
assertEquals("xyz", ((StringBean) beans[0]).name);
}
/**
* Unit test to verify that type information is included for
* all non-final types, if default typing suitably configured
*/
public void testNonFinalBean() throws Exception
{
ObjectMapper m = new ObjectMapper();
// first: use "object or abstract" typing: should produce no type info:
m.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
StringBean bean = new StringBean("x");
assertEquals("{\"name\":\"x\"}", m.writeValueAsString(bean));
// then non-final, and voila:
m = new ObjectMapper();
m.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
assertEquals("[\""+StringBean.class.getName()+"\",{\"name\":\"x\"}]",
m.writeValueAsString(bean));
}
public void testNullValue() throws Exception
{
ObjectMapper m = new ObjectMapper();
m.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
BeanHolder h = new BeanHolder();
String json = m.writeValueAsString(h);
assertNotNull(json);
BeanHolder result = m.readValue(json, BeanHolder.class);
assertNotNull(result);
assertNull(result.bean);
}
public void testEnumAsObject() throws Exception
{
// wrapping to be declared as object
Object[] input = new Object[] { Choice.YES };
Object[] input2 = new Object[] { ComplexChoice.MAYBE};
// first, without type info:
assertEquals("[\"YES\"]", serializeAsString(input));
assertEquals("[\"MAYBE\"]", serializeAsString(input2));
// and then with it
ObjectMapper m = new ObjectMapper();
m.enableDefaultTyping();
String json = m.writeValueAsString(input);
assertEquals("[[\""+Choice.class.getName()+"\",\"YES\"]]", json);
// which we should get back same way
Object[] output = m.readValue(json, Object[].class);
assertEquals(1, output.length);
assertEquals(Choice.YES, output[0]);
// ditto for more complicated enum
json = m.writeValueAsString(input2);
assertEquals("[[\""+ComplexChoice.class.getName()+"\",\"MAYBE\"]]", json);
output = m.readValue(json, Object[].class);
assertEquals(1, output.length);
assertEquals(ComplexChoice.MAYBE, output[0]);
}
@SuppressWarnings("unchecked")
public void testEnumSet() throws Exception
{
EnumSet<Choice> set = EnumSet.of(Choice.NO);
Object[] input = new Object[] { set };
ObjectMapper m = new ObjectMapper();
m.enableDefaultTyping();
String json = m.writeValueAsString(input);
Object[] output = m.readValue(json, Object[].class);
assertEquals(1, output.length);
Object ob = output[0];
assertTrue(ob instanceof EnumSet<?>);
EnumSet<Choice> set2 = (EnumSet<Choice>) ob;
assertEquals(1, set2.size());
assertTrue(set2.contains(Choice.NO));
assertFalse(set2.contains(Choice.YES));
}
@SuppressWarnings("unchecked")
public void testEnumMap() throws Exception
{
EnumMap<Choice,String> map = new EnumMap<Choice,String>(Choice.class);
map.put(Choice.NO, "maybe");
Object[] input = new Object[] { map };
ObjectMapper m = new ObjectMapper();
m.enableDefaultTyping();
String json = m.writeValueAsString(input);
Object[] output = m.readValue(json, Object[].class);
assertEquals(1, output.length);
Object ob = output[0];
assertTrue(ob instanceof EnumMap<?,?>);
EnumMap<Choice,String> map2 = (EnumMap<Choice,String>) ob;
assertEquals(1, map2.size());
assertEquals("maybe", map2.get(Choice.NO));
assertNull(map2.get(Choice.YES));
}
public void testJackson311() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String json = mapper.writeValueAsString(new PolymorphicType("hello", 2));
PolymorphicType value = mapper.readValue(json, PolymorphicType.class);
assertEquals("hello", value.foo);
assertEquals(Integer.valueOf(2), value.bar);
}
// Also, let's ensure TokenBuffer gets properly handled
public void testTokenBuffer() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// Ok, first test JSON Object containing buffer:
TokenBuffer buf = new TokenBuffer(mapper);
buf.writeStartObject();
buf.writeNumberField("num", 42);
buf.writeEndObject();
String json = mapper.writeValueAsString(new ObjectHolder(buf));
ObjectHolder holder = mapper.readValue(json, ObjectHolder.class);
assertNotNull(holder.value);
assertSame(TokenBuffer.class, holder.value.getClass());
JsonParser jp = ((TokenBuffer) holder.value).asParser();
assertToken(JsonToken.START_OBJECT, jp.nextToken());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
assertToken(JsonToken.END_OBJECT, jp.nextToken());
assertNull(jp.nextToken());
jp.close();
// then as an array:
buf = new TokenBuffer(mapper);
buf.writeStartArray();
buf.writeBoolean(true);
buf.writeEndArray();
json = mapper.writeValueAsString(new ObjectHolder(buf));
holder = mapper.readValue(json, ObjectHolder.class);
assertNotNull(holder.value);
assertSame(TokenBuffer.class, holder.value.getClass());
jp = ((TokenBuffer) holder.value).asParser();
assertToken(JsonToken.START_ARRAY, jp.nextToken());
assertToken(JsonToken.VALUE_TRUE, jp.nextToken());
assertToken(JsonToken.END_ARRAY, jp.nextToken());
assertNull(jp.nextToken());
jp.close();
// and finally as scalar
buf = new TokenBuffer(mapper);
buf.writeNumber(321);
json = mapper.writeValueAsString(new ObjectHolder(buf));
holder = mapper.readValue(json, ObjectHolder.class);
assertNotNull(holder.value);
assertSame(TokenBuffer.class, holder.value.getClass());
jp = ((TokenBuffer) holder.value).asParser();
assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
assertEquals(321, jp.getIntValue());
assertNull(jp.nextToken());
jp.close();
}
/**
* Test for [JACKSON-352]
*/
public void testIssue352() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping (ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, JsonTypeInfo.As.PROPERTY);
DiscussBean d1 = new DiscussBean();
d1.subject = "mouse";
d1.weight=88;
DomainBeanWrapper wrapper = new DomainBeanWrapper();
wrapper.name = "mickey";
wrapper.myBean = d1;
String json = mapper.writeValueAsString(wrapper);
DomainBeanWrapper result = mapper.readValue(json, DomainBeanWrapper.class);
assertNotNull(result);
assertNotNull(wrapper.myBean);
assertSame(DiscussBean.class, wrapper.myBean.getClass());
}
// Test to ensure we can also use "As.PROPERTY" inclusion and custom property name
public void testFeature432() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, "*CLASS*");
String json = mapper.writeValueAsString(new BeanHolder(new StringBean("punny")));
assertEquals("{\"bean\":{\"*CLASS*\":\"org.codehaus.jackson.map.jsontype.TestDefaultForObject$StringBean\",\"name\":\"punny\"}}", json);
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
@SuppressWarnings("unchecked")
private void _verifySerializationAsMap(String str) throws Exception
{
// First: validate that structure looks correct (as Map etc)
// note: should look something like:
// "[["org.codehaus.jackson.map.jsontype.TestDefaultForObject$StringBean",{"name":"abc"}]]")
// note: must have default mapper, default typer NOT enabled (to get 'plain' map)
ObjectMapper m = new ObjectMapper();
List<Object> list = m.readValue(str, List.class);
assertEquals(1, list.size()); // no type for main List, just single entry
Object entryOb = list.get(0);
assertTrue(entryOb instanceof List<?>);
// but then type wrapper for bean
List<?> entryList = (List<?>)entryOb;
assertEquals(2, entryList.size());
assertEquals(StringBean.class.getName(), entryList.get(0));
assertTrue(entryList.get(1) instanceof Map);
Map<?,?> map = (Map<?,?>) entryList.get(1);
assertEquals(1, map.size());
assertEquals("abc", map.get("name"));
}
}