package com.fasterxml.jackson.databind.jsontype; import java.util.*; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.type.TypeFactory; public class TestTypedDeserialization extends BaseMapTest { /* /********************************************************** /* Helper types /********************************************************** */ /** * Polymorphic base class */ @JsonTypeInfo(use=Id.CLASS, include=As.PROPERTY, property="@classy") static abstract class Animal { public String name; protected Animal(String n) { name = n; } } @JsonTypeName("doggie") static class Dog extends Animal { public int boneCount; @JsonCreator public Dog(@JsonProperty("name") String name) { super(name); } public void setBoneCount(int i) { boneCount = i; } } @JsonTypeName("kitty") static class Cat extends Animal { public String furColor; @JsonCreator public Cat(@JsonProperty("furColor") String c) { super(null); furColor = c; } public void setName(String n) { name = n; } } // Allow "empty" beans @JsonTypeName("fishy") static class Fish extends Animal { @JsonCreator public Fish() { super(null); } } // [databind#2467]: Allow missing "content" for as-array deserialization @JsonDeserialize(using = NullAnimalDeserializer.class) static class NullAnimal extends Animal { public static final NullAnimal NULL_INSTANCE = new NullAnimal(); public NullAnimal() { super(null); } } static class NullAnimalDeserializer extends JsonDeserializer { @Override public NullAnimal getNullValue(final DeserializationContext context) { return NullAnimal.NULL_INSTANCE; } @Override public NullAnimal deserialize(final JsonParser parser, final DeserializationContext context) { throw new UnsupportedOperationException(); } } static class AnimalContainer { public Animal animal; } // base class with no useful info @JsonTypeInfo(use=Id.CLASS, include=As.WRAPPER_ARRAY) static abstract class DummyBase { protected DummyBase(boolean foo) { } } static class DummyImpl extends DummyBase { public int x; public DummyImpl() { super(true); } } @JsonTypeInfo(use=Id.MINIMAL_CLASS, include=As.WRAPPER_OBJECT) interface TypeWithWrapper { } @JsonTypeInfo(use=Id.CLASS, include=As.WRAPPER_ARRAY) interface TypeWithArray { } static class Issue506DateBean { @JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type2") public Date date; } static class Issue506NumberBean { @JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type3") @JsonSubTypes({ @Type(Long.class), @Type(Integer.class) }) public Number number; } /* /********************************************************** /* Unit tests /********************************************************** */ /** * First things first, let's ensure we can serialize using * class name, written as main-level property name */ public void testSimpleClassAsProperty() throws Exception { ObjectMapper m = new ObjectMapper(); Animal a = m.readValue(asJSONObjectValueString("@classy", Cat.class.getName(), "furColor", "tabby", "name", "Garfield"), Animal.class); assertNotNull(a); assertEquals(Cat.class, a.getClass()); Cat c = (Cat) a; assertEquals("Garfield", c.name); assertEquals("tabby", c.furColor); } // Test inclusion using wrapper style public void testTypeAsWrapper() throws Exception { ObjectMapper m = new ObjectMapper(); m.addMixIn(Animal.class, TypeWithWrapper.class); String JSON = "{\".TestTypedDeserialization$Dog\" : " +asJSONObjectValueString(m, "name", "Scooby", "boneCount", "6")+" }"; Animal a = m.readValue(JSON, Animal.class); assertTrue(a instanceof Animal); assertEquals(Dog.class, a.getClass()); Dog d = (Dog) a; assertEquals("Scooby", d.name); assertEquals(6, d.boneCount); } // Test inclusion using 2-element array public void testTypeAsArray() throws Exception { ObjectMapper m = new ObjectMapper(); m.addMixIn(Animal.class, TypeWithArray.class); // hmmh. Not good idea to rely on exact output, order may change. But... String JSON = "[\""+Dog.class.getName()+"\", " +asJSONObjectValueString(m, "name", "Martti", "boneCount", "11")+" ]"; Animal a = m.readValue(JSON, Animal.class); assertEquals(Dog.class, a.getClass()); Dog d = (Dog) a; assertEquals("Martti", d.name); assertEquals(11, d.boneCount); } // Use basic Animal as contents of a regular List public void testListAsArray() throws Exception { ObjectMapper m = new ObjectMapper(); // This time using PROPERTY style (default) again String JSON = "[\n" +asJSONObjectValueString(m, "@classy", Cat.class.getName(), "name", "Hello", "furColor", "white") +",\n" // let's shuffle doggy's fields a bit for testing +asJSONObjectValueString(m, "boneCount", Integer.valueOf(1), "@classy", Dog.class.getName(), "name", "Bob" ) +",\n" +asJSONObjectValueString(m, "@classy", Fish.class.getName()) +", null\n]"; JavaType expType = TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, Animal.class); List animals = m.readValue(JSON, expType); assertNotNull(animals); assertEquals(4, animals.size()); Cat c = (Cat) animals.get(0); assertEquals("Hello", c.name); assertEquals("white", c.furColor); Dog d = (Dog) animals.get(1); assertEquals("Bob", d.name); assertEquals(1, d.boneCount); Fish f = (Fish) animals.get(2); assertNotNull(f); assertNull(animals.get(3)); } public void testCagedAnimal() throws Exception { ObjectMapper m = new ObjectMapper(); String jsonCat = asJSONObjectValueString(m, "@classy", Cat.class.getName(), "name", "Nilson", "furColor", "black"); String JSON = "{\"animal\":"+jsonCat+"}"; AnimalContainer cont = m.readValue(JSON, AnimalContainer.class); assertNotNull(cont); Animal a = cont.animal; assertNotNull(a); Cat c = (Cat) a; assertEquals("Nilson", c.name); assertEquals("black", c.furColor); } /** * Test that verifies that there are few limitations on polymorphic * base class. */ public void testAbstractEmptyBaseClass() throws Exception { DummyBase result = new ObjectMapper().readValue( "[\""+DummyImpl.class.getName()+"\",{\"x\":3}]", DummyBase.class); assertNotNull(result); assertEquals(DummyImpl.class, result.getClass()); assertEquals(3, ((DummyImpl) result).x); } // [JACKSON-506], wrt Date public void testIssue506WithDate() throws Exception { Issue506DateBean input = new Issue506DateBean(); input.date = new Date(1234L); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(input); Issue506DateBean output = mapper.readValue(json, Issue506DateBean.class); assertEquals(input.date, output.date); } // [JACKSON-506], wrt Number public void testIssue506WithNumber() throws Exception { Issue506NumberBean input = new Issue506NumberBean(); input.number = Long.valueOf(4567L); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(input); Issue506NumberBean output = mapper.readValue(json, Issue506NumberBean.class); assertEquals(input.number, output.number); } // [databind#2467]: Allow missing "content" for as-array deserialization public void testTypeAsArrayWithNullableType() throws Exception { ObjectMapper m = new ObjectMapper(); m.addMixIn(Animal.class, TypeWithArray.class); Animal a = m.readValue( "[\""+Fish.class.getName()+"\"]", Animal.class); assertNull(a); } // [databind#2467] public void testTypeAsArrayWithCustomDeserializer() throws Exception { ObjectMapper m = new ObjectMapper(); m.addMixIn(Animal.class, TypeWithArray.class); Animal a = m.readValue( "[\""+NullAnimal.class.getName()+"\"]", Animal.class); assertNotNull(a); assertEquals(NullAnimal.class, a.getClass()); NullAnimal c = (NullAnimal) a; assertNull(c.name); } }