package com.fasterxml.jackson.databind.module;
import java.io.File;
import java.io.IOException;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.node.ObjectNode;
@SuppressWarnings("serial")
public class TestCustomEnumKeyDeserializer extends BaseMapTest
{
@JsonSerialize(using = TestEnumSerializer.class, keyUsing = TestEnumKeySerializer.class)
@JsonDeserialize(using = TestEnumDeserializer.class, keyUsing = TestEnumKeyDeserializer.class)
public enum TestEnumMixin { }
enum KeyEnum {
replacements,
rootDirectory,
licenseString
}
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;
}
}
static class TestEnumSerializer extends JsonSerializer<TestEnum> {
@Override
public void serialize(TestEnum languageCode, JsonGenerator g, SerializerProvider serializerProvider) throws IOException {
g.writeString(languageCode.code());
}
@Override
public Class<TestEnum> handledType() {
return TestEnum.class;
}
}
static class TestEnumKeyDeserializer extends KeyDeserializer {
@Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
try {
return TestEnum.lookup(key);
} catch (IllegalArgumentException e) {
return ctxt.handleWeirdKey(TestEnum.class, key, "Unknown code");
}
}
}
static class TestEnumDeserializer extends StdDeserializer<TestEnum> {
public TestEnumDeserializer() {
super(TestEnum.class);
}
@Override
public TestEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String code = p.getText();
try {
return TestEnum.lookup(code);
} catch (IllegalArgumentException e) {
throw InvalidFormatException.from(p, "Undefined ISO-639 language code",
code, TestEnum.class);
}
}
}
static class TestEnumKeySerializer extends JsonSerializer<TestEnum> {
@Override
public void serialize(TestEnum test, JsonGenerator g, SerializerProvider serializerProvider) throws IOException {
g.writeFieldName(test.code());
}
@Override
public Class<TestEnum> handledType() {
return TestEnum.class;
}
}
static class Bean {
private File rootDirectory;
private String licenseString;
private Map<TestEnum, Map<String, String>> 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<TestEnum, Map<String, String>> getReplacements() {
return replacements;
}
public void setReplacements(Map<TestEnum, Map<String, String>> replacements) {
this.replacements = replacements;
}
}
static class TestEnumModule extends SimpleModule {
public TestEnumModule() {
super(Version.unknownVersion());
}
@Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(TestEnum.class, TestEnumMixin.class);
SimpleSerializers keySerializers = new SimpleSerializers();
keySerializers.addSerializer(new TestEnumKeySerializer());
context.addKeySerializers(keySerializers);
}
}
// for [databind#1441]
enum SuperTypeEnum {
FOO;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type", defaultImpl = SuperType.class)
static class SuperType {
public Map<SuperTypeEnum, String> someMap;
}
/*
/**********************************************************
/* Test methods
/**********************************************************
*/
// Test passing with the fix
public void testWithEnumKeys() throws Exception {
ObjectMapper plainObjectMapper = new ObjectMapper();
JsonNode tree = plainObjectMapper.readTree(aposToQuotes("{'red' : [ 'a', 'b']}"));
ObjectMapper fancyObjectMapper = new ObjectMapper().registerModule(new TestEnumModule());
// this line is might throw with Jackson 2.6.2.
Map<TestEnum, Set<String>> map = fancyObjectMapper.convertValue(tree,
new TypeReference<Map<TestEnum, Set<String>>>() { } );
assertNotNull(map);
}
// and another still failing
// NOTE: temporarily named as non-test to ignore it; JsonIgnore doesn't work for some reason
// public void testWithTree749() throws Exception
public void withTree749() throws Exception
{
ObjectMapper mapper = new ObjectMapper().registerModule(new TestEnumModule());
Map<KeyEnum, Object> inputMap = new LinkedHashMap<KeyEnum, Object>();
Map<TestEnum, Map<String, String>> replacements = new LinkedHashMap<TestEnum, Map<String, String>>();
Map<String, String> reps = new LinkedHashMap<String, String>();
reps.put("1", "one");
replacements.put(TestEnum.GREEN, reps);
inputMap.put(KeyEnum.replacements, replacements);
JsonNode tree = mapper.valueToTree(inputMap);
ObjectNode ob = (ObjectNode) tree;
JsonNode inner = ob.get("replacements");
String firstFieldName = inner.fieldNames().next();
assertEquals("green", firstFieldName);
}
// [databind#1441]
public void testCustomEnumKeySerializerWithPolymorphic() throws IOException
{
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(SuperTypeEnum.class, new JsonDeserializer<SuperTypeEnum>() {
@Override
public SuperTypeEnum deserialize(JsonParser p, DeserializationContext deserializationContext)
throws IOException
{
return SuperTypeEnum.valueOf(p.getText());
}
});
ObjectMapper mapper = new ObjectMapper()
.registerModule(simpleModule);
SuperType superType = mapper.readValue("{\"someMap\": {\"FOO\": \"bar\"}}",
SuperType.class);
assertEquals("Deserialized someMap.FOO should equal bar", "bar",
superType.someMap.get(SuperTypeEnum.FOO));
}
// [databind#1445]
@SuppressWarnings({ "unchecked", "rawtypes" })
public void testCustomEnumValueAndKeyViaModifier() throws IOException
{
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<Enum> modifyEnumDeserializer(DeserializationConfig config,
final JavaType type, BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new JsonDeserializer<Enum>() {
@Override
public Enum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Class<? extends Enum> rawClass = (Class<Enum<?>>) type.getRawClass();
final String str = p.getValueAsString().toLowerCase();
return KeyEnum.valueOf(rawClass, str);
}
};
}
@Override
public KeyDeserializer modifyKeyDeserializer(DeserializationConfig config,
final JavaType type, KeyDeserializer deserializer)
{
if (!type.isEnumType()) {
return deserializer;
}
return new KeyDeserializer() {
@Override
public Object deserializeKey(String key, DeserializationContext ctxt)
throws IOException
{
Class<? extends Enum> rawClass = (Class<Enum<?>>) type.getRawClass();
return Enum.valueOf(rawClass, key.toLowerCase());
}
};
}
});
ObjectMapper mapper = new ObjectMapper()
.registerModule(module);
// First, enum value as is
KeyEnum key = mapper.readValue(quote(KeyEnum.replacements.name().toUpperCase()),
KeyEnum.class);
assertSame(KeyEnum.replacements, key);
// and then as key
EnumMap<KeyEnum,String> map = mapper.readValue(
aposToQuotes("{'REPlaceMENTS':'foobar'}"),
new TypeReference<EnumMap<KeyEnum,String>>() { });
assertEquals(1, map.size());
assertSame(KeyEnum.replacements, map.keySet().iterator().next());
}
}