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