1. Overview

In Kotlin, working with JSON serialization often requires dealing with enums. When serializing these enums into JSON, we need to ensure they’re correctly converted to and from their string representations.

In this tutorial, we’ll explore how to serialize enum fields to JSON in Kotlin using some popular JSON serialization libraries.

2. Introduction to the Problem

As usual, let’s understand the problem through an example. Suppose we have an enum class representing different programming languages:

enum class Language(val description: String) {
    KOTLIN("Kotlin_is_awesome"),
    JAVA("Java_is_great"),
    GO("Go_is_nice")
}

As the Language enum shows, it has a String property: description.

Next, let’s create a data class that uses the Language enum:

data class Dev(val name: String, val language: Language)

An instance of Dev represents a developer with a name and the programming language she/he mainly works with.

Now, let’s create a Dev instance and serialize the object into JSON:

val mapper = ObjectMapper().registerModule(KotlinModule.Builder().build())
val dev = Dev("Kai", Language.KOTLIN)
val json = mapper.writeValueAsString(dev)
assertEquals("""{"name":"Kai","language":"KOTLIN"}""", json)

The above test shows that the enum’s name is used by default to represent the enum object in the serialized JSON.

We took the popular Jackson library as an example to serialize the dev object. Other JSON serialization approaches, such as Gson and kotlinx.serialization, follow the same default behavior.

However, sometimes, we want to use the value of an enum’s property instead of its name as the marshaling result in the JSON, for example, Language‘s description.

Next, let’s see how to achieve it using different serialization libraries.

3. Using the Jackson Library

Jackson’s @JsonValue annotation allows us to use the value of the annotated property as the serialized output in the result JSON.

So, let’s put @JsonValue on the description property:

enum class Language(@JsonValue val description: String) {
    KOTLIN("Kotlin_is_awesome"),
    JAVA("Java_is_great"),
    GO("Go_is_nice")
}
 
data class Dev ...

Next, let’s create a test to verify the result of the serialization:

val mapper = ObjectMapper().registerModule(KotlinModule.Builder().build())
val dev = Dev("Kai", Language.KOTLIN)
val json = mapper.writeValueAsString(dev)
assertEquals("""{"name":"Kai","language":"Kotlin_is_awesome"}""", json)

As the test shows, the @JsonValue annotation solves the problem.

4. Using the Gson Library

Gson is another popular JSON library. Two approaches can be used to customize how an enum is serialized using Gson: using the @SerializedName annotation and creating a custom serializer.

Next, let’s take a closer look at these approaches.

4.1. Using the @SerializedName Annotation

Gson’s @SerialzedName allows us to use a provided name for the annotated member in the serialized JSON.

Therefore, we can annotate every instance of an enum with @SerializedName, and use the corresponding property value as the name:

enum class Language(val description: String) {
    @SerializedName("Kotlin_is_awesome")
    KOTLIN("Kotlin_is_awesome"),
 
    @SerializedName("Java_is_great")
    JAVA("Java_is_great"),
 
    @SerializedName("Go_is_nice")
    GO("Go_is_nice")
}
 
data class Dev ...

Unlike Jackson’s @JsonValue, Gson’s @SerializedName cannot dynamically obtain a property’s value as the name. Therefore, as the enum code shows, we must copy the property values to each @SerializedName annotation.

Now, if we serialize a Dev object using Gson*, Language.description*‘s value will be in the JSON to represent the referenced Language object:

val dev = Dev("Kai", Language.GO)
val json = Gson().toJson(dev)
assertEquals("""{"name":"Kai","language":"Go_is_nice"}""", json)

As the test shows, @SerializedName solves the problem. But it’s worth noting that we have to manually add the annotation to each enum instance and copy the corresponding value. When an enum contains many instances, these operations can be error-prone.

To avoid these manual steps, we can instead create a custom serializer.

Let’s see how to do that next.

4.2. Creating a Custom Serializer

To create a custom serializer with Gson, we can inherit the JsonSerializer class and implement the serialize() function:

class LanguageSerializer : JsonSerializer<Language> {
    override fun serialize(src: Language?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
        return JsonPrimitive(requireNotNull(src).description)
    }
}

*As Language.description is a String, we can use the JsonPrimitive() function to wrap Language.description‘s value as a* JsonElement.

If we apply the serialization by a custom serializer, we can leave the enum and the data class as they are:

enum class Language(val description: String) {
    KOTLIN("Kotlin_is_awesome"),
    JAVA("Java_is_great"),
    GO("Go_is_nice")
}
 
data class Dev ...

Now, let’s tell Gson to use our LanguageSerializer and serialize a Dev object:

val gson = GsonBuilder().registerTypeAdapter(Language::class.java, LanguageSerializer()).create()
val dev = Dev("Kai", Language.GO)
val json = gson.toJson(dev)
assertEquals("""{"name":"Kai","language":"Go_is_nice"}""", json)

As the code above shows, we created a Gson object through GsonBuilder(), registered a LanguageSerializer object as a TypeAdapter, and used this Gson object to serialize the dev instance.

5. Using kotlinx.serialization

Since kotlinx.serialization is a Kotlin native and easy-to-use serialization library, it’s widely used in Kotlin projects. This library also provides two solutions to our problem: using the @SerialName annotation and creating a custom serializer.

Next, let’s have a closer look at them.

5.1. Using the @SerialName Annotation

Like Gson’s @SerializedName annotation, kotlinx.serialization’s @SerialName allows us to override the name of a class or a property in the serialized JSON. 

Its usage is also pretty similar to Gson’s @SerializedName. For example, *if we want to use Language‘s description as the name in the serialized JSON, we must add @SerialName to each enum instance and set the corresponding description value as the name.* Additionally, we must add @Serializable to the enum and the data class:

@Serializable
enum class Language(val description: String) {
    @SerialName("Kotlin_is_awesome")
    KOTLIN("Kotlin_is_awesome"),
 
    @SerialName("Java_is_great")
    JAVA("Java_is_great"),
 
    @SerialName("Go_is_nice")
    GO("Go_is_nice")
}
 
@Serializable
data class Dev ... 

Now, let’s serialize a Dev object and see if we can get the expected JSON:

val dev = Dev("Kai", Language.JAVA)
val json = Json.encodeToString(dev)
assertEquals("""{"name":"Kai","language":"Java_is_great"}""", json)

As the test shows, @SerialName solves the problem.

5.2. Creating a Custom Serializer

Alternatively, we can create a custom serializer by extending the KSerializer class:

class LanguageSerializer : KSerializer<Language> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Language", PrimitiveKind.STRING)
 
    override fun deserialize(decoder: Decoder): Language {
        val desc = decoder.decodeString()
        return Language.entries.first { desc == it.description }
    }
    override fun serialize(encoder: Encoder, value: Language) {
        encoder.encodeString(value.description)
    }
}

When we want to use LanguageSerializer for Language serialization, we can *add LanguageSerializer to Language‘s @Serializable annotation*:

@Serializable(with = LanguageSerializer::class)
enum class Language(val description: String) {
    KOTLIN("Kotlin_is_awesome"),
    JAVA("Java_is_great"),
    GO("Go_is_nice")
}
 
@Serializable
data class Dev ...

Finally, let’s examine the JSON String when we serialize a Dev object:

val dev = Dev("Kai", Language.JAVA)
val json = Json.encodeToString(dev)
assertEquals("""{"name":"Kai","language":"Java_is_great"}""", json)

As we can see, kotlinx.serialization took our LanguageSerializer to serialize the enum instance, and we got the expected result.

6. Conclusion

In this article, we explored how to perform enum JSON serialization with popular serialization libraries in Kotlin. With this knowledge, we can confidently handle converting enum values to JSON in our Kotlin projects.

As always, the complete source code for the examples is available over on GitHub.