| 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")); |
| } |
| } |