Gil's profile at Stack Overflow Coding challenges at Project Euler



Getting annotation value of an enum constant

In this post we will see how to extract the annotation value, (i.e. "choc" in @SerializedName("choc")) given an annotated enum constant. This technique is useful when we generate strings for unit tests in the context of Json deserialization. Furthermore, we will discuss some features of enums and annotations, and their connections with the reflection API.

Let's consider an example with SerializedName, an annotation from Gson, the popular Json library. The annotation can be used this way:

enum Flavor {
    @SerializedName("van")
    VANILLA,
    @SerializedName("choc")
    CHOCOLATE
}

This tells Gson to convert the Json string "van" to the enum constant VANILLA. Now we want to do the opposite, i.e. write code to go from VANILLA to "van". Let's jump right into it:

String getSerializedName(Enum<?> e) {
    try {
        Field field = e.getClass().getField(e.name());
        SerializedName annotation = field.getAnnotation(SerializedName.class);
        return annotation == null ? null : annotation.value();
    } catch (NoSuchFieldException ignored) {
        return null;
    }
}

We pass in an Enum<?> instead of a Flavor because we want our code to be as general as possible. For each enum, in fact, a class is generated, its enum type, which always extends Enum<?>. This class, in particular, contains the method name() which returns the name of the enum constant as defined in the source code. (The enum type actually extends Enum<E extends Enum<E>, but that is another topic)

Now that we know that the enum type is just a class, it is easy to retrieve the corresponding field through the reflection API: getClass(), getField(), and getAnnotation() are exactly what we are looking for. Note that getField() could in principle throw an exception (not in our case though) so we have to wrap it in a try-catch block.

Finally we return the value of the annotation if it is present. Alternatevely we could have made the check with Field.isAnnotationPresent(), without changing the logic. The "method" value() is what is actually called annotation type element and it is specified in the annotation type declaration, i.e. the interface-like declaration of SerializedName. This element has the special convention that, if it is the only element present in the annotation, it can be omitted. For example

@SerializedName(value = "van")

is equivalent to

@SerializedName("van")

To wrap up let's compare enums and annotations: both introduced in Java 5, enum types relate to classes and annotation type relate to interfaces. Without the word "type" they might mean the feature in the Java language or a specific instance of their type, i.e. an enum constant or a particular annotation on some field, method, class, etc. In the package java.lang there is a class called Enum (not to be confused with the legacy Enumeration) and an interface called Annotation. They are super types respectively of all enum types and all annotation types.