Goodbye RuntimeTypeAdapterFactory.
Polymorphic serialization using kotlinx.serialization
In an effort to use as many standard Kotlin tools as possible in our Android apps I made an effort to replace Gson with Kotlin Serialization and eventually fell in ❤ with it. Most of this was straightforward, however one thing got me thinking.
Let’s assume we have a JSON array where the type is defined by a property value:
[
{
"type": "Rect",
"width": 100,
"height": 50
},
{
"type": "Circle",
"radius": 154
}
]
With Gson deserializing this is pretty easy using a RuntimeTypeAdapterFactory
.
Luckily the latest improvements to the @Polymorphic
annotation in Kotlin Serialization 0.11.0 make this task a piece of cake as well.
To proper deserialize the above example structure, let’s define the data classes like:
@Polymorphic
@Serializable
abstract class BaseGeometric@SerialName("Rect")
@Serializable
data class Rectangle(val width: Int, val height: Int) : BaseGeometric()@SerialName("Circle")
@Serializable
data class Circle(val radius: Double) : BaseGeometric()
Note the @SerialName
at class definition! By default the full package name is used for de-/serialization, but by using @SerialName
we can define our own name.
Now we just need to register our BaseGeometric
subclasses:
val geometricsModule = SerializersModule {
polymorphic(BaseGeometric::class) {
subclass(Rectangle::class)
subclass(Circle::class)
}
}
Sample usage:
val src = listOf(Rectangle(100, 50), Circle(154.0))
Json {
serializersModule = geometricsModule
}.apply {
val json = encodeToString(src)
assert(json == "[{\"type\":\"Rect\",\"width\":100,\"height\":50},{\"type\":\"Circle\",\"radius\":154.0}]")
val polyList = decodeFromString<List<BaseGeometric>>(json)
assert(polyList == src)
}
Here we are leveraging the fact that "type"
is the default Class Discriminator in Kotlin Serialization. However this can be easily changed via JsonConfiguration
to whatever is needed. Here we are using "geo_type"
instead of "type"
:
Json {
serializersModule = geometricsModule
classDiscriminator = "geo_type"
}.apply {
val json = encodeToString(src)
assert(json == "[{\"geo_type\":\"Rect\",\"width\":100,\"height\":50},{\"geo_type\":\"Circle\",\"radius\":154.0}]")
val polyList = decodeFromString<List<BaseGeometric>>(json)
assert(polyList == src)
}
Happy serializing!