question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Generic polymorphism example doesn't work with primitive and collection types

See original GitHub issue

Describe the bug I’m trying to use example poly 16 with Response<List<String>>. However the library doesn’t seem to be able to encode List, String, etc. in a polymorphic context.

To Reproduce

import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*

@Serializable
abstract class Response<out T>

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

fun main() {
    val json = Json {
        serializersModule = SerializersModule {
            polymorphic(Response::class) {
                subclass(OkResponse.serializer(PolymorphicSerializer(Any::class)))
            }
        }
    }

    val response1: Response<List<String>> = OkResponse(listOf("good"))
    println(json.encodeToJsonElement(response1)) // Class 'SingletonList' is not registered for polymorphic serialization in the scope of 'Any'.

    val response2: Response<String> = OkResponse("good")
    println(json.encodeToJsonElement(response2)) // Class 'String' is not registered for polymorphic serialization in the scope of 'Any'.
}

However, using OkResponse instead of Response works (I guess because it’s not polymorphic):

val response: OkResponse<List<String>> = OkResponse(listOf("good"))
println(json.encodeToJsonElement(response)) // {"data":["good"]}

The error messages above aren’t helpful either. Using the guide and the error messages the next step would be straightforward:

polymorphic(Any::class) {
    subclass(SingletonList::class) // not possible because class is private
    subclass(String::class) // not possible: Serializer for String of kind STRING cannot be serialized polymorphically with class discriminator.
}

Expected behavior In the serialization guide there’s no mention about these issues. Primitives and collection types can be encoded in all other cases automatically by kotlinx-serialization. I expect the same here too.

The initial example could either output the following or add a class discriminator like kotlin.String.

{"type":"OkResponse","data":["good"]}
{"type":"OkResponse","data":"good"}

If that’s not possible, then:

  • the guide should at least explain the limitation
  • the guide should mention possible alternatives
  • the related error messages should also be improved for standard types

Environment

  • Kotlin version: 1.4.21
  • Library version: 1.0.1
  • Kotlin platforms: JVM

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:1
  • Comments:6

github_iconTop GitHub Comments

1reaction
christofvanhovecommented, Dec 16, 2020

Another option for the “not possible: Serializer for String of kind STRING cannot be serialized polymorphically with class discriminator” is useArrayPolymorphism = true. But this results in ugly unpractical json IMHO. But it just works, it’s a pitty that there is no option to serialize primitives as json object with type attributes. That would make my solution above obsolete 😉. In Kotlin primitives are just objects, so why not just serialize them like that as well?

import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
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
abstract class Response<out T>

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

fun main() {
    val json = Json {
        useArrayPolymorphism  = true
        serializersModule = SerializersModule {
            polymorphic(Any::class) {
                subclass(String::class)
            }
            polymorphic(Response::class) {
                subclass(OkResponse.serializer(PolymorphicSerializer(Any::class)))
            }
        }
    }

    val response2: Response<String> = OkResponse("good")

    //serialization looks OK
    val serializedJson = json.encodeToString(response2)
    println("serializedJson = ${serializedJson}")

    //this works
    val deSerializedJson1 = json.decodeFromString<Response<String>>(serializedJson)
    println("deSerializedJson1 = ${deSerializedJson1}")

    //this doesn't work
    try {
        json.decodeFromString<Response<Any>>(serializedJson)
    } catch (e: Exception) {
        println("why?  ${e}")
    }

    //this works
    val serializer = PolymorphicSerializer(Response::class)
    val deSerializedJson2 = json.decodeFromString(serializer, serializedJson)
    println("deSerializedJson2 = ${deSerializedJson2}")

}

1reaction
christofvanhovecommented, Dec 16, 2020

My reply is about the “not possible: Serializer for String of kind STRING cannot be serialized polymorphically with class discriminator” part of the problem. I’m not pretending to give an answer, I’m also just trying to figure things out. The following code below seems to fix your problem. Please comment if you see any issues with it 😉

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass

@Serializable
abstract class Response<out T>

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

object StringAsObjectSerializer : KSerializer<String> {

    @Serializable
    @SerialName("String")
    data class StringSurrogate(val value: String)

    override val descriptor: SerialDescriptor = StringSurrogate.serializer().descriptor

    override fun serialize(encoder: Encoder, value: String) {
        StringSurrogate.serializer().serialize(encoder, StringSurrogate(value));
    }

    override fun deserialize(decoder: Decoder): String {
        return decoder.decodeSerializableValue(StringSurrogate.serializer()).value
    }
}

fun main() {
    val json = Json {
        serializersModule = SerializersModule {
            polymorphic(Any::class) {
                subclass(StringAsObjectSerializer)
            }
            polymorphic(Response::class) {
                subclass(OkResponse.serializer(PolymorphicSerializer(Any::class)))
            }
        }
    }

    val response2: Response<String> = OkResponse("good")

    //serialization looks OK
    val serializedJson = json.encodeToString(response2)
    println("serializedJson = ${serializedJson}")

    //this works
    val deSerializedJson1 = json.decodeFromString<Response<String>>(serializedJson)
    println("deSerializedJson1 = ${deSerializedJson1}")

    //this doesn't work
    try {
        json.decodeFromString<Response<Any>>(serializedJson)
    } catch (e: Exception) {
        println("why?  ${e}")
    }

    //this works
    val serializer = PolymorphicSerializer(Response::class)
    val deSerializedJson2 = json.decodeFromString(serializer, serializedJson)
    println("deSerializedJson2 = ${deSerializedJson2}")
    
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

why polymorphism doesn't treat generic collections and plain ...
Now, the problem with Java generic types is that the type information is discarded by the compiler and it is not available at...
Read more >
Why Java Collections Cannot Directly Store Primitives Types?
Since the above two statements are true, generic Java collections can not store primitive types directly. Wrapper Class provides a way to use...
Read more >
Generic Types
parametric polymorphism. Lecture 8. CS 2112 – Spring 2012. 2. Generic Types. • When using a collection (e.g.,. LinkedList, HashSet, HashMap),.
Read more >
Parametric Polymorphism and Generics
Problem : if you instantiate with the “wrong” type argument, C++ compiler gives us long, cryptic error messages referring to the generic (templated)...
Read more >
Generic Types in Java, C++, and C - UTK EECS
Other compilers, such as the Java compiler, generate a single copy of the code which should work with all types. This is why...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found