Deserializing JSON into class where field type is marker interface for different enums fails
See original GitHub issuejackson-databind version
2.8.2 (com.fasterxml.jackson.core:jackson-databind:2.8.2)
Problem
I have found a bug related to using addAbstractTypeMapping
when adding more than one enum class for same marker interface:
com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type com.sample.JacksonEnumDeserialization$Enums2 from String “A”: value not one of declared Enum instance names: [B] at [Source: {“value”:“A”}; line: 1, column: 10](through reference chain: com.sample.Container[“value”])
Example
When I have classes:
public static class Container { // <-
private InterfaceForEnums enumValue;
// Getters/setters left out for brevity
}
// marker interface for enums
interface InterfaceForEnums {
}
public static enum Enums1 implements InterfaceForEnums {
A
}
public static enum Enums2 implements InterfaceForEnums {
B
}
and when I want to deserialize {"value":"A"}
, then registering enum classes for interface in following order would result in failure during deserialization:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
// order shouldn't matter, but matters: only works for values of enum2 (as it is added last)
simpleModule.addAbstractTypeMapping(InterfaceForEnums.class, Enums1.class);
simpleModule.addAbstractTypeMapping(InterfaceForEnums.class, Enums2.class);
objectMapper.registerModule(simpleModule);
...
objectMapperNotOK.readValue(json, Container.class)
but when registering enums in opposite order, then deserialization works for values of Enum1 (but not for values of Enum2).
I have created JUnit test to show that both enum constants can be deserialized, but with different ObjectMapper configuration, that makes it impossible to use in situation where the field could have values form either enums:
package com.sample;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
public class JacksonEnumDeserialization {
public static final String JSON_A = "{\"value\":\"A\"}";
public static final String JSON_B = "{\"value\":\"B\"}";
interface InterfaceForEnums {
}
public static enum Enums1 implements InterfaceForEnums {
A
}
public static enum Enums2 implements InterfaceForEnums {
B
}
@Data
public static class Container {
private InterfaceForEnums value;
// Getters/setters generated with lombok (using @Data)
}
private ObjectMapper createObjectMapper(
Class<? extends InterfaceForEnums>... classes) {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
for (Class<? extends InterfaceForEnums> enumClass : classes) {
simpleModule.addAbstractTypeMapping(InterfaceForEnums.class, enumClass);
}
objectMapper.registerModule(simpleModule);
return objectMapper;
}
@Test
public void testSerialization() throws Exception {
ObjectMapper objectMapper = createObjectMapper();
Assert.assertEquals(JSON_A, objectMapper.writeValueAsString(newContainer(Enums1.A)));
Assert.assertEquals(JSON_B, objectMapper.writeValueAsString(newContainer(Enums2.B)));
}
private Container newContainer(InterfaceForEnums e) {
Container object = new Container();
object.setValue(e);
return object;
}
@Test
public void testDeserializationWithEnum1OK() throws Exception {
ObjectMapper objectMapperOK = createObjectMapper(Enums2.class, Enums1.class);
Assert.assertEquals(newContainer(Enums1.A), objectMapperOK.readValue(JSON_A, Container.class));
}
@Test
public void testDeserializationWithEnum1NotOK() throws Exception {
ObjectMapper objectMapperNotOK = createObjectMapper(Enums1.class, Enums2.class);
// FIXME com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type com.sample.JacksonEnumDeserialization$Enums2 from String "A": value not one of declared Enum instance names: [B]
// at [Source: {"value":"A"}; line: 1, column: 10] (through reference chain: com.sample.Container["value"])
Assert.assertEquals(newContainer(Enums1.A), objectMapperNotOK.readValue(JSON_A, Container.class));
}
@Test
public void testDeserializationWithEnum2OK() throws Exception {
ObjectMapper objectMapperOK = createObjectMapper(Enums1.class, Enums2.class);
Assert.assertEquals(newContainer(Enums2.B), objectMapperOK.readValue(JSON_B, Container.class));
}
@Test
public void testDeserializationWithEnum2NotOK() throws Exception {
ObjectMapper objectMapperNotOK = createObjectMapper(Enums2.class, Enums1.class);
// FIXME com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type com.sample.JacksonEnumDeserialization$Enums1 from String "B": value not one of declared Enum instance names: [A]
// at [Source: {"value":"B"}; line: 1, column: 10] (through reference chain: com.sample.Container["value"])
Assert.assertEquals(newContainer(Enums2.B), objectMapperNotOK.readValue(JSON_B, Container.class));
}
}
Issue Analytics
- State:
- Created 7 years ago
- Comments:5 (4 by maintainers)
Top GitHub Comments
Thank you for reporting this and providing a reproduction! I hope to look into this soon.
@atsu85 That is not how mapping works: there can only be one explicit mapping. I may try to improve documentation in case it may have lead to wrong conclusion.
Glad to know you got it working; I do think that you need a custom deserializer here, that can use proper heuristics.