1 package com.fasterxml.jackson.databind.jsontype; 2 3 import java.util.*; 4 5 6 import com.fasterxml.jackson.annotation.*; 7 import com.fasterxml.jackson.annotation.JsonTypeInfo.As; 8 import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; 9 import com.fasterxml.jackson.annotation.JsonSubTypes.Type; 10 11 import com.fasterxml.jackson.databind.*; 12 import com.fasterxml.jackson.databind.jsontype.impl.StdSubtypeResolver; 13 import com.fasterxml.jackson.databind.type.TypeFactory; 14 15 /** 16 * Separate tests for verifying that "type name" type id mechanism 17 * works. 18 */ 19 public class TestTypeNames extends BaseMapTest 20 { 21 @SuppressWarnings("serial") 22 static class AnimalMap extends LinkedHashMap<String,Animal> { } 23 24 @JsonTypeInfo(property = "type", include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.NAME) 25 @JsonSubTypes({ 26 @JsonSubTypes.Type(value = A1616.class,name = "A"), 27 @JsonSubTypes.Type(value = B1616.class) 28 }) 29 static abstract class Base1616 { } 30 31 static class A1616 extends Base1616 { } 32 33 @JsonTypeName("B") 34 static class B1616 extends Base1616 { } 35 36 /* 37 /********************************************************** 38 /* Unit tests 39 /********************************************************** 40 */ 41 42 private final ObjectMapper MAPPER = objectMapper(); 43 testBaseTypeId1616()44 public void testBaseTypeId1616() throws Exception 45 { 46 ObjectMapper mapper = new ObjectMapper(); 47 Collection<NamedType> subtypes = new StdSubtypeResolver().collectAndResolveSubtypesByTypeId( 48 mapper.getDeserializationConfig(), 49 // note: `null` is fine here as `AnnotatedMember`: 50 null, 51 mapper.constructType(Base1616.class)); 52 assertEquals(2, subtypes.size()); 53 Set<String> ok = new HashSet<>(Arrays.asList("A", "B")); 54 for (NamedType type : subtypes) { 55 String id = type.getName(); 56 if (!ok.contains(id)) { 57 fail("Unexpected id '"+id+"' (mapping to: "+type.getType()+"), should be one of: "+ok); 58 } 59 } 60 } 61 testSerialization()62 public void testSerialization() throws Exception 63 { 64 // Note: need to use wrapper array just so that we can define 65 // static type on serialization. If we had root static types, 66 // could use those; but at the moment root type is dynamic 67 68 assertEquals("[{\"doggy\":{\"name\":\"Spot\",\"ageInYears\":3}}]", 69 MAPPER.writeValueAsString(new Animal[] { new Dog("Spot", 3) })); 70 assertEquals("[{\"MaineCoon\":{\"name\":\"Belzebub\",\"purrs\":true}}]", 71 MAPPER.writeValueAsString(new Animal[] { new MaineCoon("Belzebub", true)})); 72 } 73 testRoundTrip()74 public void testRoundTrip() throws Exception 75 { 76 Animal[] input = new Animal[] { 77 new Dog("Odie", 7), 78 null, 79 new MaineCoon("Piru", false), 80 new Persian("Khomeini", true) 81 }; 82 String json = MAPPER.writeValueAsString(input); 83 List<Animal> output = MAPPER.readValue(json, 84 TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, Animal.class)); 85 assertEquals(input.length, output.size()); 86 for (int i = 0, len = input.length; i < len; ++i) { 87 assertEquals("Entry #"+i+" differs, input = '"+json+"'", 88 input[i], output.get(i)); 89 } 90 } 91 testRoundTripMap()92 public void testRoundTripMap() throws Exception 93 { 94 AnimalMap input = new AnimalMap(); 95 input.put("venla", new MaineCoon("Venla", true)); 96 input.put("ama", new Dog("Amadeus", 13)); 97 String json = MAPPER.writeValueAsString(input); 98 AnimalMap output = MAPPER.readValue(json, AnimalMap.class); 99 assertNotNull(output); 100 assertEquals(AnimalMap.class, output.getClass()); 101 assertEquals(input.size(), output.size()); 102 103 // for some reason, straight comparison won't work... 104 for (String name : input.keySet()) { 105 Animal in = input.get(name); 106 Animal out = output.get(name); 107 if (!in.equals(out)) { 108 fail("Animal in input was ["+in+"]; output not matching: ["+out+"]"); 109 } 110 } 111 } 112 } 113 114 /* 115 /********************************************************** 116 /* Helper types 117 /********************************************************** 118 */ 119 120 @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT) 121 @JsonSubTypes({ 122 @Type(value=Dog.class, name="doggy"), 123 @Type(Cat.class) /* defaults to "TestTypedNames$Cat" then */ 124 }) 125 class Animal 126 { 127 public String name; 128 129 130 @Override equals(Object o)131 public boolean equals(Object o) { 132 if (o == this) return true; 133 if (o == null) return false; 134 if (o.getClass() != getClass()) return false; 135 return name.equals(((Animal) o).name); 136 } 137 138 @Override toString()139 public String toString() { 140 return getClass().toString() + "('"+name+"')"; 141 } 142 } 143 144 class Dog extends Animal 145 { 146 public int ageInYears; 147 Dog()148 public Dog() { } Dog(String n, int y)149 public Dog(String n, int y) { 150 name = n; 151 ageInYears = y; 152 } 153 154 @Override equals(Object o)155 public boolean equals(Object o) { 156 return super.equals(o) 157 && ((Dog) o).ageInYears == ageInYears; 158 } 159 } 160 161 @JsonSubTypes({ 162 @Type(MaineCoon.class), 163 @Type(Persian.class) 164 }) 165 abstract class Cat extends Animal { 166 public boolean purrs; Cat()167 public Cat() { } Cat(String n, boolean p)168 public Cat(String n, boolean p) { 169 name = n; 170 purrs = p; 171 } 172 173 @Override equals(Object o)174 public boolean equals(Object o) { 175 return super.equals(o) && ((Cat) o).purrs == purrs; 176 } 177 178 @Override toString()179 public String toString() { 180 return super.toString()+"(purrs: "+purrs+")"; 181 } 182 } 183 184 /* uses default name ("MaineCoon") since there's no @JsonTypeName, 185 * nor did supertype specify name 186 */ 187 class MaineCoon extends Cat { MaineCoon()188 public MaineCoon() { super(); } MaineCoon(String n, boolean p)189 public MaineCoon(String n, boolean p) { 190 super(n, p); 191 } 192 } 193 194 @JsonTypeName("persialaisKissa") 195 class Persian extends Cat { Persian()196 public Persian() { super(); } Persian(String n, boolean p)197 public Persian(String n, boolean p) { 198 super(n, p); 199 } 200 } 201 202