I have a following sealed class:
sealed class ViewModel {
data class Loaded(val value : String) : ViewModel()
object Loading : ViewModel()
}
How can I serialize/deserialize instances of the ViewModel class, let's say to/from JSON format?
I've tried to use Genson serializer/deserializer library - it can handle Kotlin data classes, it's also possible to support polymorphic types (eg. using some metadata to specify concrete types).
However, the library fails on Kotlin object
types, as these are singletons without a public constructor. I guess I could write a custom Genson converter to handle it, but maybe there's an easier way to do it?
I ended up implementing a custom Converter plus a Factory to properly plug it into Genson.
It uses Genson's metadata convention to represent the object as:
{
"@class": "com.example.ViewModel.Loading"
}
The converter assumes useClassMetadata flag set, so serialization just needs to mark an empty object. For deserialization, it resolves class name from metadata, loads it and obtains objectInstance.
object KotlinObjectConverter : Converter<Any> {
override fun serialize(objectData: Any, writer: ObjectWriter, ctx: Context) {
with(writer) {
// just empty JSON object, class name will be automatically added as metadata
beginObject()
endObject()
}
}
override fun deserialize(reader: ObjectReader, ctx: Context): Any? =
Class.forName(reader.nextObjectMetadata().metadata("class"))
.kotlin.objectInstance
.also { reader.endObject() }
}
To make sure that this converter is applied only to actual objects, I register it using a factory, that tells Genson when to use it and when to fall back to the default implementation.
object KotlinConverterFactory : Factory<Converter<Any>> {
override fun create(type: Type, genson: Genson): Converter<Any>? =
if (TypeUtil.getRawClass(type).kotlin.objectInstance != null) KotlinObjectConverter
else null
}
The factory can be used to configure Genson via builder:
GensonBuilder()
.withConverterFactory(KotlinConverterFactory)
.useClassMetadata(true) // required to add metadata during serialization
// some other properties
.create()
The code probably could be even nicer with chained converters feature, but I didn't have time to check it out yet.