Kotlin with Jackson: Deserializing Kotlin Sealed Classes

Serhii Prodan
3 min readApr 9, 2019

--

Recently I needed to deserialize Kotlin sealed class into a CSV. Let’s look at how this can be done.

According to official Kotlin reference guide for Sealed Classes:

Sealed classes are used for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type. They are, in a sense, an extension of enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances which can contain state.

They are quite handy indeed and when I’m writing in Kotlin I generally prefer sealed classes over enums. I will not discuss sealed classes themselves in this article, let’s look at the limitation I faced when deserializing a sealed class into CSV file.

Consider the following class:

data class Data(
val id: String,
val parameterType: String,
val parameterValue: String
)

And a CSV schema to deserialize from:

id,parameterType,parameterValue

If we want to restrict our parameter types (which is usually a good idea) we could use an enum class or a sealed class:

data class Data(
val id: String,
val parameterType: Parameter,
val parameterValue: String
)
sealed class Parameter(val value: String)object FirstName: Parameter("First Name")
object FullName : Parameter("Full Name")

We have our Parameter class, which is a sealed class and two objects implementing it FirstName and FullName.

This might not be the best case to use sealed classes. After all, both our implementations are object‘s and therefore can also exist only as a single instance (same as enums), however for simplicity’s sake let’s keep it like this.

Sealed classes come with some restrictions though, among which:

A sealed class is abstract by itself, it cannot be instantiated directly

Therefore Jackson will not be able to instantiate our Parameter class directly, and if we try to deserialize our csv data to this class we will get:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Parameter` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

I didn’t want to create a custom deserializer for this case, this seems like an overkill to me. Therefore I looked into implementing a creator function for the sealed class:

sealed class Parameter(val value: String) {companion object {
@JsonCreator
@JvmStatic
private fun creator(name: String): Parameter? {
// some logic here
}
}
}

@JsonCreator is an annotation from Jackson:

Marker annotation that can be used to define constructors and factory methods as one to use for instantiating new instances of the associated class.

and @JvmStatic is an annotation from Kotlin that allows us to have this function generated as real static method for Java (See java interop for more details.)

So what do we put inside the creator() function?

If it were an enum class we could, for example, deserialize using the enum class name:

enum class EnumParameter(val value: String) {
FIRST_NAME("Fist Name"),
FULL_NAME("Full Name");
companion object {
@JsonCreator
@JvmStatic
private fun creator(name: String): EnumParameter? {
return EnumParameter.values().firstOrNull { it.name == name }
}
}
}

And our CSV to deserialize from would look something like this:

id,parameterName,parameterValue
1,FIRST_NAME,Jane
2,FULL_NAME,John Smith

Can we do the same with sealed classes? Yes! Luckily since Kotlin 1.3 we have sealedSubclasses property for KClasstype that returns the list of the immediate subclasses if this class is a sealed class.

So using some reflection magic the above code for enum class converted to work with a sealed class would look like this:

sealed class Parameter(val value: String) {companion object {
@JsonCreator
@JvmStatic
private fun creator(name: String): Parameter? {
return Parameter::class.sealedSubclasses.firstOrNull { it.simpleName == name }?.objectInstance
}
}
}

And our CSV:

id,parameterName,parameterValue
1,First Name,Jane
2,Full Name,John Smith

Unfortunately if we’re using Kotlin 1.2 or lower we don’t have the sealedSubclasses property available and I haven’t found any other way how to let Jackson know which implementation of the sealed class to instantiate. If you know a way please let me know and I will update the article.

--

--

Serhii Prodan
Serhii Prodan

Written by Serhii Prodan

Searching for the answer to the Ultimate Question by night, tester by calling, serendipitously became a devops lead by day. Automating things I get my hands on.

Responses (3)