• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.module;
2 
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.*;
6 
7 import com.fasterxml.jackson.annotation.JsonTypeInfo;
8 import com.fasterxml.jackson.core.*;
9 import com.fasterxml.jackson.core.type.TypeReference;
10 
11 import com.fasterxml.jackson.databind.*;
12 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
13 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
14 import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
15 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
16 import com.fasterxml.jackson.databind.exc.InvalidFormatException;
17 import com.fasterxml.jackson.databind.node.ObjectNode;
18 
19 @SuppressWarnings("serial")
20 public class TestCustomEnumKeyDeserializer extends BaseMapTest
21 {
22     @JsonSerialize(using = TestEnumSerializer.class, keyUsing = TestEnumKeySerializer.class)
23     @JsonDeserialize(using = TestEnumDeserializer.class, keyUsing = TestEnumKeyDeserializer.class)
24     public enum TestEnumMixin { }
25 
26     enum KeyEnum {
27         replacements,
28         rootDirectory,
29         licenseString
30     }
31 
32     enum TestEnum {
33         RED("red"),
34         GREEN("green");
35 
36         private final String code;
37 
TestEnum(String code)38         TestEnum(String code) {
39             this.code = code;
40         }
41 
lookup(String lower)42         public static TestEnum lookup(String lower) {
43             for (TestEnum item : values()) {
44                 if (item.code().equals(lower)) {
45                     return item;
46                 }
47             }
48             throw new IllegalArgumentException("Invalid code " + lower);
49         }
50 
code()51         public String code() {
52             return code;
53         }
54     }
55 
56     static class TestEnumSerializer extends JsonSerializer<TestEnum> {
57         @Override
serialize(TestEnum languageCode, JsonGenerator g, SerializerProvider serializerProvider)58         public void serialize(TestEnum languageCode, JsonGenerator g, SerializerProvider serializerProvider) throws IOException {
59             g.writeString(languageCode.code());
60         }
61 
62         @Override
handledType()63         public Class<TestEnum> handledType() {
64             return TestEnum.class;
65         }
66     }
67 
68     static class TestEnumKeyDeserializer extends KeyDeserializer {
69         @Override
deserializeKey(String key, DeserializationContext ctxt)70         public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
71             try {
72                 return TestEnum.lookup(key);
73             } catch (IllegalArgumentException e) {
74                 return ctxt.handleWeirdKey(TestEnum.class, key, "Unknown code");
75             }
76         }
77     }
78 
79     static class TestEnumDeserializer extends StdDeserializer<TestEnum> {
TestEnumDeserializer()80         public TestEnumDeserializer() {
81             super(TestEnum.class);
82         }
83 
84         @Override
deserialize(JsonParser p, DeserializationContext ctxt)85         public TestEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
86             String code = p.getText();
87             try {
88                 return TestEnum.lookup(code);
89             } catch (IllegalArgumentException e) {
90                 throw InvalidFormatException.from(p, "Undefined ISO-639 language code",
91                         code, TestEnum.class);
92             }
93         }
94     }
95 
96     static class TestEnumKeySerializer extends JsonSerializer<TestEnum> {
97         @Override
serialize(TestEnum test, JsonGenerator g, SerializerProvider serializerProvider)98         public void serialize(TestEnum test, JsonGenerator g, SerializerProvider serializerProvider) throws IOException {
99             g.writeFieldName(test.code());
100         }
101 
102         @Override
handledType()103         public Class<TestEnum> handledType() {
104             return TestEnum.class;
105         }
106     }
107 
108     static class Bean {
109         private File rootDirectory;
110         private String licenseString;
111         private Map<TestEnum, Map<String, String>> replacements;
112 
getRootDirectory()113         public File getRootDirectory() {
114             return rootDirectory;
115         }
116 
setRootDirectory(File rootDirectory)117         public void setRootDirectory(File rootDirectory) {
118             this.rootDirectory = rootDirectory;
119         }
120 
getLicenseString()121         public String getLicenseString() {
122             return licenseString;
123         }
124 
setLicenseString(String licenseString)125         public void setLicenseString(String licenseString) {
126             this.licenseString = licenseString;
127         }
128 
getReplacements()129         public Map<TestEnum, Map<String, String>> getReplacements() {
130             return replacements;
131         }
132 
setReplacements(Map<TestEnum, Map<String, String>> replacements)133         public void setReplacements(Map<TestEnum, Map<String, String>> replacements) {
134             this.replacements = replacements;
135         }
136     }
137 
138     static class TestEnumModule extends SimpleModule {
TestEnumModule()139         public TestEnumModule() {
140             super(Version.unknownVersion());
141         }
142 
143         @Override
setupModule(SetupContext context)144         public void setupModule(SetupContext context) {
145             context.setMixInAnnotations(TestEnum.class, TestEnumMixin.class);
146             SimpleSerializers keySerializers = new SimpleSerializers();
147             keySerializers.addSerializer(new TestEnumKeySerializer());
148             context.addKeySerializers(keySerializers);
149         }
150     }
151 
152     // for [databind#1441]
153 
154     enum SuperTypeEnum {
155         FOO;
156     }
157 
158     @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type", defaultImpl = SuperType.class)
159     static class SuperType {
160         public Map<SuperTypeEnum, String> someMap;
161     }
162 
163     /*
164     /**********************************************************
165     /* Test methods
166     /**********************************************************
167      */
168 
169     // Test passing with the fix
testWithEnumKeys()170     public void testWithEnumKeys() throws Exception {
171         ObjectMapper plainObjectMapper = new ObjectMapper();
172         JsonNode tree = plainObjectMapper.readTree(aposToQuotes("{'red' : [ 'a', 'b']}"));
173 
174         ObjectMapper fancyObjectMapper = new ObjectMapper().registerModule(new TestEnumModule());
175 
176         // this line is might throw with Jackson 2.6.2.
177         Map<TestEnum, Set<String>> map = fancyObjectMapper.convertValue(tree,
178                 new TypeReference<Map<TestEnum, Set<String>>>() { } );
179         assertNotNull(map);
180     }
181 
182     // and another still failing
183     // NOTE: temporarily named as non-test to ignore it; JsonIgnore doesn't work for some reason
184 //    public void testWithTree749() throws Exception
withTree749()185     public void withTree749() throws Exception
186     {
187         ObjectMapper mapper = new ObjectMapper().registerModule(new TestEnumModule());
188 
189         Map<KeyEnum, Object> inputMap = new LinkedHashMap<KeyEnum, Object>();
190         Map<TestEnum, Map<String, String>> replacements = new LinkedHashMap<TestEnum, Map<String, String>>();
191         Map<String, String> reps = new LinkedHashMap<String, String>();
192         reps.put("1", "one");
193         replacements.put(TestEnum.GREEN, reps);
194         inputMap.put(KeyEnum.replacements, replacements);
195 
196         JsonNode tree = mapper.valueToTree(inputMap);
197         ObjectNode ob = (ObjectNode) tree;
198 
199         JsonNode inner = ob.get("replacements");
200         String firstFieldName = inner.fieldNames().next();
201         assertEquals("green", firstFieldName);
202     }
203 
204     // [databind#1441]
testCustomEnumKeySerializerWithPolymorphic()205     public void testCustomEnumKeySerializerWithPolymorphic() throws IOException
206     {
207         SimpleModule simpleModule = new SimpleModule();
208         simpleModule.addDeserializer(SuperTypeEnum.class, new JsonDeserializer<SuperTypeEnum>() {
209             @Override
210             public SuperTypeEnum deserialize(JsonParser p, DeserializationContext deserializationContext)
211                     throws IOException
212             {
213                 return SuperTypeEnum.valueOf(p.getText());
214             }
215         });
216         ObjectMapper mapper = new ObjectMapper()
217                 .registerModule(simpleModule);
218 
219         SuperType superType = mapper.readValue("{\"someMap\": {\"FOO\": \"bar\"}}",
220                 SuperType.class);
221         assertEquals("Deserialized someMap.FOO should equal bar", "bar",
222                 superType.someMap.get(SuperTypeEnum.FOO));
223     }
224 
225     // [databind#1445]
226     @SuppressWarnings({ "unchecked", "rawtypes" })
testCustomEnumValueAndKeyViaModifier()227     public void testCustomEnumValueAndKeyViaModifier() throws IOException
228     {
229         SimpleModule module = new SimpleModule();
230         module.setDeserializerModifier(new BeanDeserializerModifier() {
231             @Override
232             public JsonDeserializer<Enum> modifyEnumDeserializer(DeserializationConfig config,
233                     final JavaType type, BeanDescription beanDesc,
234                     final JsonDeserializer<?> deserializer) {
235                 return new JsonDeserializer<Enum>() {
236                     @Override
237                     public Enum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
238                         Class<? extends Enum> rawClass = (Class<Enum<?>>) type.getRawClass();
239                         final String str = p.getValueAsString().toLowerCase();
240                         return KeyEnum.valueOf(rawClass, str);
241                     }
242                 };
243             }
244 
245             @Override
246             public KeyDeserializer modifyKeyDeserializer(DeserializationConfig config,
247                     final JavaType type, KeyDeserializer deserializer)
248             {
249                 if (!type.isEnumType()) {
250                     return deserializer;
251                 }
252                 return new KeyDeserializer() {
253                     @Override
254                     public Object deserializeKey(String key, DeserializationContext ctxt)
255                             throws IOException
256                     {
257                         Class<? extends Enum> rawClass = (Class<Enum<?>>) type.getRawClass();
258                         return Enum.valueOf(rawClass, key.toLowerCase());
259                     }
260                 };
261             }
262         });
263         ObjectMapper mapper = new ObjectMapper()
264                 .registerModule(module);
265 
266         // First, enum value as is
267         KeyEnum key = mapper.readValue(quote(KeyEnum.replacements.name().toUpperCase()),
268                 KeyEnum.class);
269         assertSame(KeyEnum.replacements, key);
270 
271         // and then as key
272         EnumMap<KeyEnum,String> map = mapper.readValue(
273                 aposToQuotes("{'REPlaceMENTS':'foobar'}"),
274                 new TypeReference<EnumMap<KeyEnum,String>>() { });
275         assertEquals(1, map.size());
276         assertSame(KeyEnum.replacements, map.keySet().iterator().next());
277     }
278 }
279