Kotlinx.serialization: Consider all Serializable class implements a common interface.

Created on 12 Jan 2019  路  6Comments  路  Source: Kotlin/kotlinx.serialization

Consider all Serializable class implements a common interface to get the corresponding KSerializer for that class (and this should be done by the compiler plugin).

@Serializable
data class Test(
        val name: String
)

the generated code maybe like this

interface KSerializable<T> { // ignore the dummy name (because Serializable is already taken)
    val serializer: KSerializer<T>
}

@Serializable
data class Test(
        val name: String
) : KSerializable<Test> {
    override val serializer
        get() = Test.serializer()
}

For now, one can only serialize (and deserialize) an object like this:

        val kotlinMessage = buildKotlinMessage()
        val kotlinOut = ProtoBuf.plain.dump(Kotlin.Message.serializer(), kotlinMessage)

A serializer must be passed, so we must know the exactly class type.

By this, one can serialize an object easily:

        val kotlinMessage = buildKotlinMessage()
        val kotlinOut = ProtoBuf.plain.dump(kotlinMessage.serializer, kotlinMessage)
        // or if ProtoBuf takes advantage of this interface, we can also do this
        val kotlinOut = ProtoBuf.plain.dump(kotlinMessage)

Which looks much better!

design feature

Most helpful comment

I came here just to ask for it, but for a different purpose: I want to create a type-safe API, that checks at compile-time, whether a class is serializable or not.

For example, say I have a generic function that serializes an object using the experimental KClass.serializer() function (to retrieve the class serializer using Reflection):

fun sendToClient(response: Any) {
    ...
    val json = Json.stringify(response::class.serializer(), response)
    ...
}

Consumers of this function will be able to call it with not-serializable instances, because it has the Any type.

However, if an interface (like, for example, KSerializable) gets added to every @Serializable-annotated class, then I could rewrite this function as:

fun sendToClient(response: KSerializable) {
    ...
    val json = Json.stringify(response::class.serializer(), response)
    ...
}

... And, suddenly, compile-time checking! :)

Also, it could be used to further improve the signature of KClass.serializer() from:

fun <T : Any> KClass<T>.serializer(): KSerializer<T> = ...

To:

fun <T : KSerializable> KClass<T>.serializer(): KSerializer<T> = ...

And, suddenly, compile-time checking again.

All 6 comments

Yeah, this is a great idea but it will work only with non-generic classes because classes with generic parameters have .serializer function which accepts parameters. It could be a useful shorthand, but not a silver bullet for a 'retrieve serializer' problem.

But for generic classes can be used some parameters. For example:

@Serializable(SomeExactSerializer::class)
class ItIsSerializable<SomeClass> {鈥

It is in case of unavailable solving of serializer in compile time or:

@Serializable
class ItIsSerializable<ItIsAlsoaserializable> {鈥

As and for primitives and other built-in serializable types:

@Serializable
class ItIsSerializable<Long> {鈥

For each type will be used interface KSerializable, but in fact its built-in serializer may have any type of generic. Please, correct me if I am wrong.

This would be really useful; it would allow adding extension functions to classes that are serializable

I came here just to ask for it, but for a different purpose: I want to create a type-safe API, that checks at compile-time, whether a class is serializable or not.

For example, say I have a generic function that serializes an object using the experimental KClass.serializer() function (to retrieve the class serializer using Reflection):

fun sendToClient(response: Any) {
    ...
    val json = Json.stringify(response::class.serializer(), response)
    ...
}

Consumers of this function will be able to call it with not-serializable instances, because it has the Any type.

However, if an interface (like, for example, KSerializable) gets added to every @Serializable-annotated class, then I could rewrite this function as:

fun sendToClient(response: KSerializable) {
    ...
    val json = Json.stringify(response::class.serializer(), response)
    ...
}

... And, suddenly, compile-time checking! :)

Also, it could be used to further improve the signature of KClass.serializer() from:

fun <T : Any> KClass<T>.serializer(): KSerializer<T> = ...

To:

fun <T : KSerializable> KClass<T>.serializer(): KSerializer<T> = ...

And, suddenly, compile-time checking again.

All proposed use-cases are valid, but unfortunately, this feature cannot be implemented in a way that works all the time consistently.
Classes with type parameters are one part of the problem. E.g. does List<T> implement KSerializable<T>? If so, what should its serializer function return?
Another problem is Kotlin built-in types: primitives, collections, String etc. that obviously cannot implement an interface from the 3rd-party library. All interfaces are implicitly marked with @Polymorphic.
Also, there are 3-rd party classes that are only externally serializable (@Serializer(forClass = ...)).

Taking this into account, it doesn't seem possible to implement this with a reasonable effort. In the future, if Kotlin has typeclasses, it will be possible to solve this problem using them though.

Closing as won't fix.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kastork picture kastork  路  3Comments

lonevetad picture lonevetad  路  3Comments

czeidler picture czeidler  路  4Comments

kdabir picture kdabir  路  3Comments

raderio picture raderio  路  3Comments