diff --git a/pom.xml b/pom.xml index aa9b960479..9b9972e1ae 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,12 @@ javax.xml.datatype, javax.xml.namespace, javax.xml.parsers 0.9.2 test + + com.google.guava + guava + 18.0 + test + diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index 8a737aade0..e1c5d47029 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1387,16 +1387,23 @@ private KeyDeserializer _createEnumKeyDeserializer(DeserializationContext ctxt, throws JsonMappingException { final DeserializationConfig config = ctxt.getConfig(); + Class enumClass = type.getRawClass(); + BeanDescription beanDesc = config.introspect(type); - JsonDeserializer des = findDeserializerFromAnnotation(ctxt, beanDesc.getClassInfo()); + // 24-Sep-2015, bim: a key deserializer is the preferred thing. + KeyDeserializer des = findKeyDeserializerFromAnnotation(ctxt, beanDesc.getClassInfo()); if (des != null) { - return StdKeyDeserializers.constructDelegatingKeyDeserializer(config, type, des); - } - Class enumClass = type.getRawClass(); - // 23-Nov-2010, tatu: Custom deserializer? - JsonDeserializer custom = _findCustomEnumDeserializer(enumClass, config, beanDesc); - if (custom != null) { - return StdKeyDeserializers.constructDelegatingKeyDeserializer(config, type, custom); + return des; + } else { + // 24-Sep-2015, bim: if no key deser, look for enum deserializer first, then a plain deser. + JsonDeserializer custom = _findCustomEnumDeserializer(enumClass, config, beanDesc); + if (custom != null) { + return StdKeyDeserializers.constructDelegatingKeyDeserializer(config, type, custom); + } + JsonDeserializer valueDesForKey = findDeserializerFromAnnotation(ctxt, beanDesc.getClassInfo()); + if (valueDesForKey != null) { + return StdKeyDeserializers.constructDelegatingKeyDeserializer(config, type, valueDesForKey); + } } EnumResolver enumRes = constructEnumResolver(enumClass, config, beanDesc.findJsonValueMethod()); @@ -1728,6 +1735,22 @@ protected JsonDeserializer findDeserializerFromAnnotation(Deserializatio return ctxt.deserializerInstance(ann, deserDef); } + /** + * Helper method called to check if a class or method + * has annotation that tells which class to use for deserialization. + * Returns null if no such annotation found. + */ + protected KeyDeserializer findKeyDeserializerFromAnnotation(DeserializationContext ctxt, + Annotated ann) + throws JsonMappingException + { + Object deserDef = ctxt.getAnnotationIntrospector().findKeyDeserializer(ann); + if (deserDef == null) { + return null; + } + return ctxt.keyDeserializerInstance(ann, deserDef); + } + /** * Method called to see if given method has annotations that indicate * a more specific type than what the argument specifies. diff --git a/src/test/java/com/fasterxml/jackson/databind/module/TestCustomKeyDeserializer.java b/src/test/java/com/fasterxml/jackson/databind/module/TestCustomKeyDeserializer.java new file mode 100644 index 0000000000..686da30ff3 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/TestCustomKeyDeserializer.java @@ -0,0 +1,56 @@ + +package com.fasterxml.jackson.databind.module; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.customenumkey.KeyEnum; +import com.fasterxml.jackson.databind.module.customenumkey.TestEnum; +import com.fasterxml.jackson.databind.module.customenumkey.TestEnumModule; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Maps; +import com.google.common.io.Resources; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.assertEquals; + +/** + * + */ +public class TestCustomKeyDeserializer { + @Test + public void troubleWithKeys() throws Exception { + ObjectMapper plainObjectMapper = new ObjectMapper(); + JsonNode tree = plainObjectMapper.readTree(Resources.getResource("data/enum-custom-key-test.json")); + ObjectMapper fancyObjectMapper = TestEnumModule.setupObjectMapper(new ObjectMapper()); + // this line is might throw with Jackson 2.6.2. + Map> map = fancyObjectMapper.convertValue(tree, new TypeReference>>() { + }); + assertNotNull(map); + } + + @Ignore("issue 749, more or less") + @Test + public void tree() throws Exception { + + Map inputMap = Maps.newHashMap(); + Map> replacements = Maps.newHashMap(); + Map reps = Maps.newHashMap(); + reps.put("1", "one"); + replacements.put(TestEnum.GREEN, reps); + inputMap.put(KeyEnum.replacements, replacements); + ObjectMapper mapper = TestEnumModule.setupObjectMapper(new ObjectMapper()); + JsonNode tree = mapper.valueToTree(inputMap); + ObjectNode ob = (ObjectNode) tree; + JsonNode inner = ob.get("replacements"); + String firstFieldName = inner.fieldNames().next(); + assertEquals("green", firstFieldName); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/Bean.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/Bean.java new file mode 100644 index 0000000000..e0f6fc678d --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/Bean.java @@ -0,0 +1,39 @@ + + +package com.fasterxml.jackson.databind.module.customenumkey; + +import java.io.File; +import java.util.Map; + +/** + * + */ +public class Bean { + private File rootDirectory; + private String licenseString; + private Map> replacements; + + public File getRootDirectory() { + return rootDirectory; + } + + public void setRootDirectory(File rootDirectory) { + this.rootDirectory = rootDirectory; + } + + public String getLicenseString() { + return licenseString; + } + + public void setLicenseString(String licenseString) { + this.licenseString = licenseString; + } + + public Map> getReplacements() { + return replacements; + } + + public void setReplacements(Map> replacements) { + this.replacements = replacements; + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/KeyEnum.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/KeyEnum.java new file mode 100644 index 0000000000..9b918bb79c --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/KeyEnum.java @@ -0,0 +1,10 @@ + +package com.fasterxml.jackson.databind.module.customenumkey; + +/** + */ +public enum KeyEnum { + replacements, + rootDirectory, + licenseString +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/ModuleVersion.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/ModuleVersion.java new file mode 100644 index 0000000000..01e9c9e777 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/ModuleVersion.java @@ -0,0 +1,28 @@ +/****************************************************************************** + ** This data and information is proprietary to, and a valuable trade secret + ** of, Basis Technology Corp. It is given in confidence by Basis Technology + ** and may only be used as permitted under the license agreement under which + ** it has been distributed, and in no other way. + ** + ** Copyright (c) 2014 Basis Technology Corporation All rights reserved. + ** + ** The technical data and information provided herein are provided with + ** `limited rights', and the computer software provided herein is provided + ** with `restricted rights' as those terms are defined in DAR and ASPR + ** 7-104.9(a). + ******************************************************************************/ + +package com.fasterxml.jackson.databind.module.customenumkey; + +import com.fasterxml.jackson.core.Version; + +/** + * Common class to set up Jackson version from property. + */ +final class ModuleVersion { + static final Version VERSION = new Version(0, 0, 0, "", "", ""); + + private ModuleVersion() { + // + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnum.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnum.java new file mode 100644 index 0000000000..4ab57d56e7 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnum.java @@ -0,0 +1,42 @@ +/****************************************************************************** + * * This data and information is proprietary to, and a valuable trade secret + * * of, Basis Technology Corp. It is given in confidence by Basis Technology + * * and may only be used as permitted under the license agreement under which + * * it has been distributed, and in no other way. + * * + * * Copyright (c) 2015 Basis Technology Corporation All rights reserved. + * * + * * The technical data and information provided herein are provided with + * * `limited rights', and the computer software provided herein is provided + * * with `restricted rights' as those terms are defined in DAR and ASPR + * * 7-104.9(a). + ******************************************************************************/ + +package com.fasterxml.jackson.databind.module.customenumkey; + +/** + * + */ +public enum TestEnum { + RED("red"), + GREEN("green"); + + private final String code; + + TestEnum(String code) { + this.code = code; + } + + public static TestEnum lookup(String lower) { + for (TestEnum item : values()) { + if (item.code().equals(lower)) { + return item; + } + } + throw new IllegalArgumentException("Invalid code " + lower); + } + + public String code() { + return code; + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumDeserializer.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumDeserializer.java new file mode 100644 index 0000000000..1d4d37af8f --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumDeserializer.java @@ -0,0 +1,29 @@ + +package com.fasterxml.jackson.databind.module.customenumkey; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; + +import java.io.IOException; + +/** + * + */ +public class TestEnumDeserializer extends StdDeserializer { + + public TestEnumDeserializer() { + super(TestEnum.class); + } + + @Override + public TestEnum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + String code = jp.getText(); + try { + return TestEnum.lookup(code); + } catch (IllegalArgumentException e) { + throw new InvalidFormatException("Undefined ISO-639 language code", jp.getCurrentLocation(), code, TestEnum.class); + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumKeyDeserializer.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumKeyDeserializer.java new file mode 100644 index 0000000000..5ad8402539 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumKeyDeserializer.java @@ -0,0 +1,21 @@ + +package com.fasterxml.jackson.databind.module.customenumkey; + +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.KeyDeserializer; + +import java.io.IOException; + +/** + * + */ +public class TestEnumKeyDeserializer extends KeyDeserializer { + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { + try { + return TestEnum.lookup(key); + } catch (IllegalArgumentException e) { + throw ctxt.weirdKeyException(TestEnum.class, key, "Unknown code"); + } + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumKeySerializer.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumKeySerializer.java new file mode 100644 index 0000000000..b9a10b358f --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumKeySerializer.java @@ -0,0 +1,23 @@ + +package com.fasterxml.jackson.databind.module.customenumkey; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * Jackson serializer for LanguageCode used as a key. + */ +public class TestEnumKeySerializer extends JsonSerializer { + @Override + public void serialize(TestEnum test, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeFieldName(test.code()); + } + + @Override + public Class handledType() { + return TestEnum.class; + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumMixin.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumMixin.java new file mode 100644 index 0000000000..ae4f9193f9 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumMixin.java @@ -0,0 +1,11 @@ + + +package com.fasterxml.jackson.databind.module.customenumkey; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@JsonSerialize(using = TestEnumSerializer.class, keyUsing = TestEnumKeySerializer.class) +@JsonDeserialize(using = TestEnumDeserializer.class, keyUsing = TestEnumKeyDeserializer.class) +public enum TestEnumMixin { +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumModule.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumModule.java new file mode 100644 index 0000000000..fbb2b9896a --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumModule.java @@ -0,0 +1,27 @@ + + +package com.fasterxml.jackson.databind.module.customenumkey; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.module.SimpleSerializers; + +public class TestEnumModule extends SimpleModule { + + public TestEnumModule() { + super(ModuleVersion.VERSION); + } + + public void setupModule(SetupContext context) { + context.setMixInAnnotations(TestEnum.class, TestEnumMixin.class); + SimpleSerializers keySerializers = new SimpleSerializers(); + keySerializers.addSerializer(new TestEnumKeySerializer()); + context.addKeySerializers(keySerializers); + } + + public static ObjectMapper setupObjectMapper(ObjectMapper mapper) { + final TestEnumModule module = new TestEnumModule(); + mapper.registerModule(module); + return mapper; + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumSerializer.java b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumSerializer.java new file mode 100644 index 0000000000..34830ea826 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/module/customenumkey/TestEnumSerializer.java @@ -0,0 +1,36 @@ +/****************************************************************************** + ** This data and information is proprietary to, and a valuable trade secret + ** of, Basis Technology Corp. It is given in confidence by Basis Technology + ** and may only be used as permitted under the license agreement under which + ** it has been distributed, and in no other way. + ** + ** Copyright (c) 2014 Basis Technology Corporation All rights reserved. + ** + ** The technical data and information provided herein are provided with + ** `limited rights', and the computer software provided herein is provided + ** with `restricted rights' as those terms are defined in DAR and ASPR + ** 7-104.9(a). + ******************************************************************************/ + +package com.fasterxml.jackson.databind.module.customenumkey; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * Jackson serializer for LanguageCode. + */ +public class TestEnumSerializer extends JsonSerializer { + @Override + public void serialize(TestEnum languageCode, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(languageCode.code()); + } + + @Override + public Class handledType() { + return TestEnum.class; + } +} diff --git a/src/test/resources/data/enum-custom-key-test.json b/src/test/resources/data/enum-custom-key-test.json new file mode 100644 index 0000000000..6570b8daca --- /dev/null +++ b/src/test/resources/data/enum-custom-key-test.json @@ -0,0 +1,3 @@ +{ + "red" : [ "a", "b"] +}