Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.31k stars 619 forks source link

null values of generic types in polymorphic classes throw NPE #1604

Open lnhrdt opened 3 years ago

lnhrdt commented 3 years ago

To understand how to use polymorphism and generic classes I've followed the section on it in the polymorphism docs. However when the generic value is null it throws an exception. Here's an example based on the code in the docs.

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass

@Serializable
sealed class Response<out T>

@Serializable
@SerialName("OkResponse")
data class OkResponse<out T>(val data: T) : Response<T>()

@Serializable
data class Item(val message: String)

fun main() {
    val json = Json {
        serializersModule = SerializersModule {
            polymorphic(Any::class) {
                subclass(Item::class)
            }
        }
    }

    println(json.encodeToString<Response<Item?>>(OkResponse(Item("hello")))) // {"type":"OkResponse","data":{"type":"Item","message":"hello"}}
    println(json.encodeToString<Response<Item?>>(OkResponse(null))) // throws (see below)
}

exception:

Exception in thread "main" java.lang.NullPointerException: null cannot be cast to non-null type kotlin.Any
    at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:273)
    at kotlinx.serialization.encoding.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:80)
    at OkResponse.write$Self(example.kt:12)
    at OkResponse$$serializer.serialize(example.kt:12)
    at OkResponse$$serializer.serialize(example.kt:12)
    at kotlinx.serialization.json.internal.StreamingJsonEncoder.encodeSerializableValue(StreamingJsonEncoder.kt:275)
    at kotlinx.serialization.json.Json.encodeToString(Json.kt:73)
    at ExampleKt.main(example.kt:35)
    at ExampleKt.main(example.kt)
sandwwraith commented 3 years ago

subclass(Item::class) does not allow null values — it's a serializer for Response<Item>, not Response<Item?>. Try subclass(Item.serializer().nullable)

lnhrdt commented 3 years ago

Changing my example to:

serializersModule = SerializersModule {
    polymorphic(Any::class) {
        subclass(Item.serializer().nullable)
    }
}

Results in this compilation error:

Screen Shot 2021-07-19 at 6 36 21 AM

None of the following functions can be called with the arguments supplied. PolymorphicModuleBuilder<TypeVariable(Base)>.subclass(KClass<TypeVariable(T)>)   where Base = TypeVariable(Base), T = TypeVariable(T) for   inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder.subclass(clazz: KClass): Unit defined in kotlinx.serialization.modules PolymorphicModuleBuilder<TypeVariable(Base)>.subclass(KSerializer<TypeVariable(T)>)   where Base = TypeVariable(Base), T = TypeVariable(T) for   inline fun <Base : Any, reified T : Base> PolymorphicModuleBuilder.subclass(serializer: KSerializer): Unit defined in kotlinx.serialization.modules

Thanks for trying to help. Any more ideas?

sandwwraith commented 3 years ago

Hm, looks like that this part wasn't well thought out. You can try subclass<Item>(Item.serializer().nullable as KSerializer<Item>) but now I'm not sure that we support nullable polymorphism at all

lnhrdt commented 3 years ago

@sandwwraith I tried your suggestion but it fails with the same error

null cannot be cast to non-null type kotlin.Any

Thanks for the fast help, even if my scenario turns out to not be supported in this library. Is it likely to be supported soon?