0

I have a data class that inherits from a sealed class that I want to serialize into a java.util.ConcurrentLinkedQueue.

@Serializable
sealed class A

@Serializable
data class B : A {
    val x: String,
    val y: Int,
    val z: Boolean
}

@Serializable
data class C : A {
    val a: String,
    val b: Int,
    val c: Boolean
}

@Serializable
data class MyServiceCache(
    val someOtherProp = "Hello World",
    @Serializable(with = ConcurrentLinkedQueueSerializer::class)
    val networkCallQueue: ConcurrentLinkedQueue<A> = ConcurrentLinkedQueue(),
)

I also have a custom serializer

class ConcurrentLinkedQueueSerializer<T>(
    private val dataSerializer: KSerializer<List<T>>
) : KSerializer<ConcurrentLinkedQueue<T>> {
    override val descriptor: SerialDescriptor = dataSerializer.descriptor
    override fun serialize(encoder: Encoder, value: ConcurrentLinkedQueue<T>) = dataSerializer.serialize(
        encoder,
        value.toList()
    )
    override fun deserialize(decoder: Decoder) = ConcurrentLinkedQueue(dataSerializer.deserialize(decoder))
}

This is all good at compile time. When I attempt to serialize in my application at runtime, I get the following error:

Caused by: java.lang.IllegalArgumentException: Polymorphic value has not been read for class null
    at kotlinx.serialization.internal.AbstractPolymorphicSerializer.deserialize(AbstractPolymorphicSerializer.kt:67)
    at com.my.app.service.network.ConcurrentLinkedQueueSerializer.deserialize(MyServiceCache.kt:35)
    at com.my.app.service.network.ConcurrentLinkedQueueSerializer.deserialize(MyServiceCache.kt:27)
    at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:69)
    at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
    at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
    at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableElement(StreamingJsonDecoder.kt:168)
    at com.my.app.service.network.MyServiceCache$$serializer.deserialize(MyServiceCache.kt:16)
    at com.my.app.service.network.MyServiceCache$$serializer.deserialize(MyServiceCache.kt:16)
    at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:69)
    at kotlinx.serialization.json.Json.decodeFromString(Json.kt:107)

What's wrong with my custom serializer? I tried to follow the instructions in the documentation as best I could. https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#custom-serializers-for-a-generic-type

1 Answer 1

2

The guidance you linked says this:

A generic class serializer is instantiated with serializers for its generic parameters.

What this means is that, when the framework instantiates a serializer for a generic class (say MyClass<T>) (with that serializer implementing KSerializer<MyClass<T>>) it passes an object of type KSerializer<T> to it. It does this so that serializer has the tool it needs to serialize whatever generic object(s) it contains.

You instead have put your surrogate serializer as the constructor parameter.

To follow the instructions, you need to rewrite with the serializer for the generic type T as constructor parameter. This will be provided by the framework at runtime:

class ConcurrentLinkedQueueSerializer<T>(
    serializerForGenericType: KSerializer<T>
) : KSerializer<ConcurrentLinkedQueue<T>> {
    private val surrogateListSerializer = ListSerializer(serializerForGenericType)

    override val descriptor: SerialDescriptor = surrogateListSerializer.descriptor

    override fun serialize(encoder: Encoder, value: ConcurrentLinkedQueue<T>) {
        surrogateListSerializer.serialize(encoder, value.toList())
    }
        
    override fun deserialize(decoder: Decoder) = ConcurrentLinkedQueue(surrogateListSerializer.deserialize(decoder))
}

The other thing done here is we have had to instantiate your surrogate serializer surrogateListSerializer by hand (using the built-in ListSerializer) using the provided generic serializer as parameter to do so (following the same above rule for serializers of generic classes).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.