I am making a data structures that automatically persist data to the disk. To do this I am using kotlinx-serialization. Is there any way to require a generic type to have the @Serializabe annotation?
class PersistentList <@Serializable E> () : List<E> {...}
This example doesn't compile. Hopefully you understand what I mean. I want to constrain E to have the annotation. Any way to do that?
CodePudding user response:
KSerializer
constructor parameter
You add a KSerializer
for E
in the constructor of PersistentList
.
class PersistentList<E>(
elementSerializer: KSerializer<E>
) : List<E> {
// ...
}
While more verbose, it has an advantage over adding an annotation to E
- a custom serializer could be assigned, meaning that PersistentList
could contain classes from external code that do not have the @Serializeable
annotation.
import kotlinx.serialization.KSerializer
// Assume this class is from another library, meaning @Serializable cannot be added
data class SomeExternalData(
val name: String
}
// Create a custom serializer for SomeExternalData
object SomeExternalDataSerializer : KSerializer<SomeExternalData> {
// ...
}
fun main() {
// Since a serializer is required, the `E` must be serializable
val persistentList = PersistentList(SomeExternalDataSerializer)
}
class PersistentList< E: PersistentElement>(
elementSerializer: KSerializer<E>,
) : List<E> {
// ...
}
That's the only feasible way to ensure that E
is serializable.
SerializersModule.serializer()
To make it a little more automatic, you could use a SerializersModule
, which has a function, serializer()
, that can fetch the serializer via reflection magic.
It's possible to create an extension function that works like Json.encodeToString(...)
, that will automatically determine the serializer for a specific format.
/** Automatically determine the serializable type of [PersistentList] */
inline fun <reified T> SerialFormat.createPersistentList(): PersistentList<SomeData> =
PersistentList(serializersModule.serializer())
However, this would not trigger any compile-time checks.
Here's a more complete example:
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialFormat
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
@Serializable
data class SomeData(
val name: String
)
fun main() {
val persistentList = Json.createPersistentList(listOf(
SomeData("1"), SomeData("2"), SomeData("3"),
))
println(persistentList)
val encoded = Json.encodeToString(persistentList.listSerializer, persistentList.actualList)
println(encoded)
}
class PersistentList<E>(
elementSerializer: KSerializer<E>,
val actualList: List<E>,
) : List<E> by actualList {
val listSerializer = ListSerializer(elementSerializer)
}
inline fun <reified E> SerialFormat.createPersistentList(actualList: List<E>): PersistentList<E> =
PersistentList(serializersModule.serializer(), actualList)