avro-kotlin / avro4k

Avro format support for Kotlin
Apache License 2.0
188 stars 36 forks source link

Support for sealed interfaces (across multiple files) #107

Closed williamboxhall closed 1 year ago

williamboxhall commented 2 years ago

I'm using a sealed interface and when I try to generate an avro schema for the sealed hierarchy I get:

Exception in thread "main" kotlinx.serialization.SerializationException: Unsupported type kotlinx.serialization.Polymorphic<SkillsCoachPublishedEvent> of OPEN
    at com.sksamuel.avro4k.schema.SchemaForKt.schemaFor(SchemaFor.kt:160)
    at com.sksamuel.avro4k.schema.ClassSchemaFor.buildField(ClassSchemaFor.kt:70)
    at com.sksamuel.avro4k.schema.ClassSchemaFor.dataClassSchema(ClassSchemaFor.kt:55)
    at com.sksamuel.avro4k.schema.ClassSchemaFor.schema(ClassSchemaFor.kt:43)
    at com.sksamuel.avro4k.Avro.schema(Avro.kt:239)
    at events.AvroSchemaGeneratorKt.main(AvroSchemaGenerator.kt:12)

it looks like kotlinx-serialization thinks this class is SerialKind.OPEN instead of SerialKind.SEALED so I guess this could be a kotlinx-serialization issue with sealed interfaces (not sure if being across multiple files matters)

williamboxhall commented 2 years ago

Related issue over in kotlinx-serialization https://github.com/Kotlin/kotlinx.serialization/issues/1417

rocketraman commented 2 years ago

Did you find a workaround for this @williamboxhall ?

williamboxhall commented 2 years ago

@rocketraman no workaround unfortunately - luckily for me I was able to get away with translating my heirarchy into a sealed class rather than using sealed interfaces. But I know this won't always be possible in future so would love proper support for this.

thake commented 2 years ago

Implemented with #111

thake commented 2 years ago

As it turns out there are multiple problems with supporting sealed interfaces. As it seems, the upcoming kotlin 1.6.20 will support sealed interfaces natively (see https://github.com/JetBrains/kotlin/commit/de128a5406fcbc4cfcfdc7f136eea0e1885dddb0 and https://github.com/Kotlin/kotlinx.serialization/issues/1576). Therefore the custom implementation will be removed until the offical support exists. When sealed interfaces will be officially supported by kotlinx.serialization, this ticket should be used to check the avro4k compatibility.

If you still need (partial) support for sealed interfaces in the mean time, you can use the following code snippet to register the serializers:

@file:OptIn(InternalSerializationApi::class)
@file:Suppress("UNCHECKED_CAST")
package com.github.avrokotlin.avro4k
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.modules.SerializersModuleBuilder
import kotlinx.serialization.serializer
import kotlin.reflect.KClass

/**
 * Registers all direct subclasses of a sealed type as polymorphic types.
 */
fun <T : Any> SerializersModuleBuilder.polymorphicForSealed(sealedType: KClass<T>) {
    if (!sealedType.isSealed) throw IllegalArgumentException(
        "Type $sealedType is not sealed."
    )
    sealedType.sealedSubclasses.forEach {
        polymorphic(sealedType, it as KClass<T>, it.serializer())
    }
}

/**
 * Registers all subclasses of a sealed type tree as polymorphic types. In contrast to polymorphicForSealed this method
 * also adds subclasses of sealed subclasses as polymorphic types.
 */
fun <T : Any> SerializersModuleBuilder.polymorphicTreeForSealed(sealedType: KClass<T>) {
    if (!sealedType.isSealed) throw IllegalArgumentException(
        "Type $sealedType is not a sealed type."
    )
    sealedType.sealedSubclasses.forEach {
        polymorphic(sealedType, it as KClass<T>, it.serializer())
        if(it.isSealed) {
            polymorphicTreeForSealed(it)
        }
    }
}
thake commented 1 year ago

Current main supports sealed interfaces