Home > Back-end >  Is there any way to require an annotation on a type parameter in kotlin?
Is there any way to require an annotation on a type parameter in kotlin?

Time:11-29

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)
  • Related