Skip to content

Custom generic serializer gets UnitSerializer as input incorrectly #1843

@UnknownJoe796

Description

@UnknownJoe796

A generated generic serializer containing a property with custom generic serializer gives UnitSerializer to said custom serializer instead of the correct type argument.

The code below outputs:

kotlinx.serialization.internal.UnitSerializer@6895a785
Exception in thread "main" java.lang.ClassCastException: class com.test.Thing cannot be cast to class kotlin.Unit (com.test.Thing and kotlin.Unit are in unnamed module of loader 'app')
	at kotlinx.serialization.internal.UnitSerializer.serialize(Primitives.kt:79)
	at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:219)
	at com.test.BoxSerializer.serialize(brokenTest.kt:30)
	at com.test.BoxSerializer.serialize(brokenTest.kt:16)
	at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:219)
	at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:80)
	at com.test.Query.write$Self(brokenTest.kt:35)
	at com.test.Query$$serializer.serialize(brokenTest.kt:35)
	at com.test.Query$$serializer.serialize(brokenTest.kt:35)
	at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:219)
	at kotlinx.serialization.json.Json.encodeToString(Json.kt:85)
	at com.test.BrokenTestKt.main(brokenTest.kt:50)
	at com.test.BrokenTestKt.main(brokenTest.kt)

To Reproduce

@file:OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
package com.test

import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.Json
import java.lang.IllegalStateException

@Serializable(BoxSerializer::class)
sealed class Box<T> {
    @Serializable data class BoxA<T>(val value: T): Box<T>()
}

class BoxSerializer<T>(val inner: KSerializer<T>): KSerializer<Box<T>> {

    init {
        println(inner)
    }

    override fun deserialize(decoder: Decoder): Box<T> {
        return Box.BoxA(decoder.decodeSerializableValue(inner))
    }

    override val descriptor: SerialDescriptor
        get() = SerialDescriptor("Box<${inner.descriptor.serialName}>", inner.descriptor)

    override fun serialize(encoder: Encoder, value: Box<T>) {
        encoder.encodeSerializableValue(inner, (value as Box.BoxA<T>).value)
    }

}

@Serializable
data class Query<T>(
    val condition: Box<T>
)

@Serializable
data class Thing(
    val x: Int = 0
)

val json = Json

fun main() {
    println(json.encodeToString(Query(Box.BoxA(Thing(2)))))
}

Expected behavior

When using a custom serializer, whether or not the class is sealed or abstract should be irrelevant and the argument to the custom serializer should be the correct type, not UnitSerializer.

The code above would work in this case.

Workaround

Mark your class as open and protect the constructor.

Environment

  • Kotlin version: 1.6.10
  • Library version: 1.6.10
  • Kotlin platforms: JVM
  • Gradle version: 7.1

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions