Skip to content

encodeToJsonElement crashes with generic ContextualSerializer #2535

Open
@httpdispatch

Description

@httpdispatch

Describe the bug
When i try to encode generic data to the JSON element using ContextualSerializer, the following crash occurs

Empty list doesn't contain element at index 0.
java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 0.
	at kotlin.collections.EmptyList.get(Collections.kt:37)
	at kotlin.collections.EmptyList.get(Collections.kt:25)
	at GenericContextualTest$createJson$1$1$1.invoke(GenericContextualTest.kt:55)
	at GenericContextualTest$createJson$1$1$1.invoke(GenericContextualTest.kt:55)
	at kotlinx.serialization.modules.ContextualProvider$WithTypeArguments.invoke(SerializersModule.kt:231)
	at kotlinx.serialization.modules.SerialModuleImpl.getContextual(SerializersModule.kt:172)
	at kotlinx.serialization.modules.SerializersModule.getContextual$default(SerializersModule.kt:48)
	at kotlinx.serialization.descriptors.ContextAwareKt.getContextualDescriptor(ContextAware.kt:61)
	at kotlinx.serialization.json.internal.WriteModeKt.carrierDescriptor(WriteMode.kt:49)
	at kotlinx.serialization.json.internal.AbstractJsonTreeEncoder.encodeSerializableValue(TreeJsonEncoder.kt:79)
	at kotlinx.serialization.json.internal.TreeJsonEncoderKt.writeJson(TreeJsonEncoder.kt:21)
	at kotlinx.serialization.json.Json.encodeToJsonElement(Json.kt:117)
	at GenericContextualTest.should serialize contextual to JsonElement(GenericContextualTest.kt:37)

To Reproduce

import kotlinx.serialization.ContextualSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.modules.SerializersModule
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

class GenericContextualTest {

    @Test
    fun `should serialize contextual to string`() {
        val json = createJson()
        val serializer = createContextualSerializer(
            typeArgumentSerializer = String.serializer(),
        )

        val actualResult = json.encodeToString(
            serializer,
            Box("test")
        )

        assertThat(actualResult).isEqualTo("\"test\"")
    }

    @Test
    fun `should deserialize contextual from string`() {
        val json = createJson()
        val serializer = createContextualSerializer(
            typeArgumentSerializer = String.serializer(),
        )

        val actualResult = json.decodeFromString(
            serializer,
            "\"test\""
        )

        assertThat(actualResult).isEqualTo(Box("test"))
    }

    @Test
    fun `should serialize contextual to JsonElement`() {
        val json = createJson()
        val serializer = createContextualSerializer(
            typeArgumentSerializer = String.serializer(),
        )

        val actualResult = json.encodeToJsonElement(
            serializer,
            Box("test")
        )

        assertThat(actualResult).isEqualTo(JsonPrimitive("test"))
    }

    @Test
    fun `should deserialize contextual from JsonElement`() {
        val json = createJson()
        val serializer = createContextualSerializer(
            typeArgumentSerializer = String.serializer(),
        )

        val actualResult = json.decodeFromJsonElement(
            serializer,
            JsonPrimitive("test")
        )

        assertThat(actualResult).isEqualTo(Box("test"))
    }

    fun <T> createContextualSerializer(
        typeArgumentSerializer: KSerializer<T>,
    ): KSerializer<Box<T>> = ContextualSerializer(
        serializableClass = Box::class,
        fallbackSerializer = null,
        typeArgumentsSerializers = arrayOf(typeArgumentSerializer),
    ) as KSerializer<Box<T>>

    private fun createJson() = Json {
        serializersModule = SerializersModule {
            contextual(Box::class) { args -> BoxSerializer(args[0]) }
        }
    }

    data class Box<T>(val contents: T)

    class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> {
        override val descriptor: SerialDescriptor = dataSerializer.descriptor
        override fun serialize(encoder: Encoder, value: Box<T>) =
            dataSerializer.serialize(encoder, value.contents)

        override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder))
    }
}

Expected behavior

Serialization should be performed properly without crash. Deserialization works as expected

Environment

  • Kotlin version: 1.9.21
  • Library version: 1.6.2
  • Kotlin platforms: JVM
  • Gradle version: 8.5
  • IDE version: Android Studio Hedgehog | 2023.1.1
  • Other relevant context: Windows 10, Java 17

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions