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