Doing Enums with Jackson

Recently I’ve been working on a Jersey 2 application (a JAX-RS REST) using JSON. For the JSON serialization and deserialization I’ve chosen to use the Jackson framework. One of the reasons for this is that prior we’ve been using Freemarker templates, but if you use those, you can easily create invalid JSON which will eventually screw up your clients. Using Jackson & Jersey allow me to skip the entire Object graph to template to output chain and simply use POJOs and use annotations to model the JSON request and responses.

So basically, if I have a request/response that has a “number”, I can use those annotations to expose getter and setter methods, or choose to ignore Jackson adding a default. So in the next fragment, the getter getSomeArbitraryNumber() will be written to “number”, any incoming “number” will be mapped to setSomeArbitraryNumber and when serializing, Jackson will ignore the isNumberSet() method (without the JsonIgnore() it will expose “numberSet”):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    ...
    @JsonProperty("number")
    public Long getSomeArbitraryNumber() {
        return number;
    }
 
    @JsonProperty("number")
    public Long setSomeArbitraryNumber(Long number) {
        this.number = number;
        this.numberSet = true;
    }
 
    @JsonIgnore()
    public boolean isNumberSet() {
       return numberSet;
    }
    ...

Anyway, long story short, I ran into issues with Enums, simply because, well, you can’t instantiate a new Enum. Exposing them as JSON objects wasn’t that hard, you can just add the @JsonFormat(shape=JsonFormat.Shape.OBJECT) annotation to the class and Jackson will serialize the Enum as if it was a simple POJO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package net.sirious.jackson.enum
 
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
 
@JsonFormat(shape=JsonFormat.Shape.OBJECT)
public enum MyEnumType {
 
    TYPE_ONE("one", "this is basically just type one"),
    TYPE_TWO("two", "and this is type two");
 
    private final String id;
    private final String description;
 
    private MyEnumType(final String id, final String description) {
        this.id = id;
        this.description = description;
    }
 
    @JsonProperty("id")
    public String getId() {
        return id;
    }
 
    @JsonProperty("description")
    public String getDescription() {
        return description;
    }
 
    /**
     * Gets a MyEnumType from id or <tt>null</tt> if the requested type doesn't exist.
     * @param id String
     * @return MyEnumType
     */
    public static MyEnumType fromId(final String id) {
        if (id != null) {
            for (MyEnumType type : MyEnumType.values()) {
                if (id.equalsIgnoreCase(type.id)) {
                    return type;
                }
            }
        }
        return null;
    }
 
}

So if I had a pojo with a getter like this:

1
2
3
4
5
6
7
    ...
    @JsonProperty("myenumtype")
    public MyEnumType getMyEnumType() {
        return myEnumType;
    }
 
}

then Jackson will serialize MyEnumType.TYPE_ONE as follows:

1
2
3
4
5
6
   ...
   "myenumtype": {
      "id":"one", 
      "description":"this is basically just type one"
   }
   ...

However, deserializing is an issue, as normally Jackson expects a no-arg constructor, but you can’t have that with Enums. So you need something that can do this for you, which is a JsonDeserializer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package package net.sirious.jackson.enum.deserializer;
 
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import net.sirious.jackson.enum.MyEnumType;
import java.io.IOException;
 
public class RecurrenceEndTypeDeserializer extends JsonDeserializer {
 
    @Override
    public MyEnumType deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
 
        MyEnumType type = MyEnumType.fromId(jp.getValueAsString());
        if (type != null) {
            return type;
        }
        throw new JsonMappingException("invalid value for type, must be 'one' or 'two'");
    }
 
}

Above deserializer now allows you to accept Strings, either “one” or “two” in the JSON:

1
2
3
4
5
{
   ...
   "myenumtype":"one" 
   ...
}

And then you can use this on your POJO as follows:

1
2
3
4
5
6
7
8
    ...
    @JsonProperty("myenumtype")
    @JsonDeserialize(using = MyEnumTypeDeserializer.class)
    public void setMyEnumType(MyEnumType value) {
        this.myEnumType = value;
    }
 
}

And that’s it! Jackson will automatically get the String “one”, use the deserializer to get a MyEnumType and then pass that on your POJO. You can use this method to customize any serialization/deserialization with Jackson.