Moshi & Kotlin's sealed classes

Helder Pinhal
Dec 25 2020
Posted in Engineering & Technology

Using Moshi's Polymorphic adapter to handle JSON with sealed classes

Sealed classes

Sealed classes are another new concept brought to us by Kotlin. They provide us with a way to represent a constrained set of possibilities where an object can only be one of the given types.

Take the following example. We can have various types of notifications, but our object is of a single type.

sealed class Notification {
    class Alert : Notification()
    class Video : Notification()
    class DeepLink : Notification()
    class DigitalCard : Notification()
}

val notification: Notification = Notification.Alert()

While being similar to enums, sealed classes are supercharged with some other features like holding properties specific to a given subtype.

Enter Moshi

Most of the applications nowadays are using Moshi to handle JSON. In version 1.8.0 Moshi introduced the PolymorphicJsonAdapterFactory which allows us to convert certain JSON structures into sealed classes.

Along the lines of the above sealed class, consider the following JSON data.

[
  {
    "type": "alert",
    "title": "...",
    "message": "..."
  },
  {
    "type": "video",
    "title": "...",
    "message": "...",
    "videoUrl": "..."
  },
  {
    "type": "deeplink",
    title": "...",
    "message": "...",
    "deeplink": "..."
  },
  {
    "type": "digitalCard",
    "title": "...",
    "message": "...",
    "cardId": "..."
  }
]

Although the original sealed class would work, we do need to adjust it to leverage the additional properties in the JSON data and adding Moshi's codegen annotations.

@JsonClass(generateAdapter = true)
sealed class Notification {

    @JsonClass(generateAdapter = true)
    data class Alert(
        val title: String,
        val message: String,
    ) : Notification()

    @JsonClass(generateAdapter = true)
    data class Video(
        val title: String,
        val message: String,
        val videoUrl: String,
    ) : Notification()

    @JsonClass(generateAdapter = true)
    data class DeepLink(
        val title: String,
        val message: String,
        val deeplink: String,
    ) : Notification()

    @JsonClass(generateAdapter = true)
    data class DigitalCard(
        val title: String,
        val message: String,
        val cardId: String,
    ) : Notification()
}

Configuring the adapter

Moshi makes the whole process quite simple. One has to create a PolymorphicJsonAdapterFactory, indicate which JSON property it should use to identify the concrete class to use (type in our case), link the concrete classes with their JSON string representation and, lastly, add the factory to the Moshi builder.

val factory = PolymorphicJsonAdapterFactory.of(Notification::class.java, "type")
    .withSubtype(Notification.Alert::class.java, "alert")
    .withSubtype(Notification.Video::class.java, "video")
    .withSubtype(Notification.DeepLink::class.java, "deeplink")
    .withSubtype(Notification.DigitalCard::class.java, "digitalCard")

val moshi = Moshi.Builder()
    .add(factory)
    .build()

Final thoughts

The whole process is quite simple and the end result very convenient for a Kotlin developer. If you're okay with yet another dependency, you can take a look at MoshiX which simplifies the process even further.

If you liked this article or have something to add, we are available, as always, via our Support Channel.

Keep up-to-date with the latest news