avro-kotlin / avro4k

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

Deserialize polymorphic open class #135

Closed michaelpetri closed 1 year ago

michaelpetri commented 1 year ago

Hey,

i'm currently trying to deserialize a list of a polymorphic open class, but this fails with Kind OPEN is currently not supported., which seems like some limitation? But i wonder why, since it's also possible to generate a schema for it, which you also cover within your tests.

A solution is to make the BaseEvent sealed, which will probably work for me but prevents me to build a package which can be used by my other packages.

So my question, do i miss something or is it just a limitation for some reason?

Here an example code:

package example

import com.github.avrokotlin.avro4k.Avro
import com.github.avrokotlin.avro4k.io.AvroDecodeFormat
import com.github.avrokotlin.avro4k.io.AvroEncodeFormat
import kotlinx.serialization.Serializable
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.serializer
import org.apache.avro.generic.GenericData
import org.junit.jupiter.api.Test
import java.io.ByteArrayOutputStream
import kotlin.test.assertEquals

@Serializable
abstract class BaseEvent {
    abstract val id: Int
}

@Serializable
data class SubEvent(
    override val id: Int,
    val value: String,
) : BaseEvent()

@Serializable
data class AnotherEvent(
    override val id: Int,
    val value: String,
) : BaseEvent()

@Serializable
data class History(
    val events: List<BaseEvent>
)

val module = SerializersModule {
    include(Avro.defaultModule)
    polymorphic(BaseEvent::class, SubEvent::class, serializer())
    polymorphic(BaseEvent::class, AnotherEvent::class, serializer())
}

class SerializationTest {

    @Test
    fun `serialize and deserialize list of polymorphic open classes`() {
        val history = History(
            listOf(
                SubEvent(
                    1,
                    "Foo"
                ),
                AnotherEvent(
                    2,
                    "Bar"
                )
            )
        )

        val historySerializer = History.serializer()
        val avro = Avro(module)
        val historySchema = avro.schema(historySerializer)
        val buffer = ByteArrayOutputStream()

        avro.openOutputStream(historySerializer) {
            encodeFormat = AvroEncodeFormat.Binary
            schema = historySchema
        }.to(buffer).write(history).close()

        avro.openInputStream {
            decodeFormat = AvroDecodeFormat.Binary(historySchema)
        }.from(buffer.toByteArray()).iterator().forEach {
            val reconstructedHistory = avro.fromRecord(historySerializer, it as GenericData.Record)

            assertEquals(history, reconstructedHistory)
        }
    }
}

Thanks :v:

PS: The code in this repo is locked to kotlinx.serialization 1.4.0-RC, do you think it makes sense to update to latest version in order to give people more up2date examples? I could open a PR if you like.

thake commented 1 year ago

Thanks for reporting the issue! I'll have a look at it and will surely update the dependency.

thake commented 1 year ago

Just to give you a heads up, updating the deps is unfortunately not so easy because some tests fail with kotlinx.serialization 1.4.1.

thake commented 1 year ago

kotlinx.serialization 1.4.1 has a bug that does not allow us to use it. Therefore we'll only update to 1.4.0.

I've added the missing test cases to the code base and fixed the problem. The current main should be able to handle open classes in lists/maps. Fixed with 57946b59011df2ea16233dec572f53047b37f523. I'll prepare a release.

michaelpetri commented 1 year ago

Thanks for the fast response and fix. :muscle: