Jackson – Deserialization from json to Java enums
If you program in Java and use jackson as a serialization library, you may have noticed that enums are a bit tricky. By default they are serialized as the name of enum value, but this in some cases one need to proceed in a different way, for example by serializing the underlying numeric value, or by using the toString() method instead of name(). In fact jackson provides many different methods to do so without recurring to custom serializers. The most complete collection of all these methods can be found in jackson’s own enum serialization unit tests.
But serialization is only half of the job, sometimes you need also to get those enums back into a java object. In general there is no issue, as long as you configure your mapper to deserialize the same way you serializes. Enum serialized as objects are a different matter though.
If you serialize to an object by using the annotation @JsonFormat(shape = JsonFormat.Shape.OBJECT)
you won’t be able to deserialize
without some extra work. According to the documentation,
“there is no specific handling for deserialization currently: but you can use @JsonCreator on a static method, to handle deserialization”
(this is from old documentation, but it seems to be still true in more recent releases).
An example of how it is supposed to work:
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
@JsonDeserialize(using = MyEnumDeserializer.class)
public enum MyEnum {
Value1("Value1"),
Value2("Value2"),
Value3("Value2");
@JsonProperty("value")
private String value;
MyEnum(String value) {
this.value = value;
}
@JsonCreator
public static MyEnum fromValue(final JsonNode jsonNode) {
for (MyEnum type : MyEnum.values()) {
if (type.value.equals(jsonNode.get("value").asText())) {
return type;
}
}
return null;
}
}
This approach has two issues. First of all it doesn’t really work with jackson 2.5, since in case of enums jackson will complain that “Parameter #0 type for factory method ([method it.righele.MyEnum#fromValue(1 params)]) not suitable, must be java.lang.String or int/Integer/long/Long”. Moreover it doesn’t work if your enum has more than one argument, which is a bit stupid, since the object representation is the most useful right in this case.
This means that you have to resort to a custom deserializer. The following example works with jackson 2.5, and probably even older versions:
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
@JsonDeserialize(using = MyEnum.MyEnumDeserializer.class)
public enum MyEnum {
ENUM1("key1", "value1");
ENUM2("key2", "value2");
private String key;
private String value;
private MyEnum(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public static class MyEnumDeserializer extends StdDeserializer<MyEnum> {
public MyEnumDeserializer() {
super(MyEnum.class);
}
@Override
public MyEnum deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
final JsonNode jsonNode = jp.readValueAsTree();
String key = jsonNode.get("key").asText();
String value = jsonNode.get("value").asText();
for (MyEnum me: MyEnum.values()) {
if ( me.getKey().equals(key) && me.getValue().equals(value)) {
return me;
}
}
throw dc.mappingException("Cannot deserialize MyEnum from key " + key + " and value " + value);
}
}
}
I hope I will remember about this next time I have to map another enum… :)