Ktor: Deserialize a list of generics inside a data class with kotlinx.serialization

Created on 10 Apr 2020  路  13Comments  路  Source: ktorio/ktor

Say you have:

@Serializable
data class PagedData<T>(val data: T, val page: Int, val totalPages: Int, val pageSize: Int)

Where you know for sure that T is a class that has the annotations @Serializable or a list of a type that is annotated with @Serializable.

Why the serialization runtime is not able to infer what to use? It would be kind of pointless to explicitly build a serializer before every response as such.

Cross reference on kotlin.serialization issue board

feature

Most helpful comment

Hi, @lamba92. I reproduced the problem on the server, it will be fixed in the next minor release.

All 13 comments

Hi, @lamba92. I reproduced the problem on the server, it will be fixed in the next minor release.

Any ETA for the next minor release?

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.

Don't know if this is new or not but with a combination of Plugin generated generic serializer with Serializers module this is possible.

@Serializable
data class Item(val entry: String)

@Serializable
data class PagedData<T>(val data: T, val page: Int, val totalPages: Int, val pageSize: Int)

// in Application.module()

json(json = Json {
    serializersModule = SerializersModule {
        contextual(PagedData.serializer(Item.serializer()))
    }
})

With these changes on your response in ktor you no longer need to serialize the object in call.response() method to a String

@monxalo after set contextual(PagedData.serializer(Item.serializer()))

Exception in thread "main" java.lang.NoSuchMethodError: kotlinx.serialization.modules.SerializersModuleBuilder.build()Lkotlinx/serialization/modules/SerializersModule;

image

Solved!

After added to dependencies org.jetbrains.kotlinx:kotlinx-serialization-runtime:1.0-M1-1.4.0-rc

before that there was only io.ktor: ktor-serialization:1.4.1

@monxalo
how do you apply your solution to an array?

contextual(PagedData.serializer(List<Item>.serializer())) ???

@graycatdeveloper

If you are using a list might better define it in the PagedData class like so:

@Serializable
data class PagedData<T>(val data: List<T>, val page: Int, val totalPages: Int, val pageSize: Int)

The contextual remains the same:

contextual(PagedData.serializer(Item.serializer()))

Drawback of this approach is that SerializersModule only allows one serializer for each type. So you if do:

contextual(PagedData.serializer(Item.serializer()))
contextual(PagedData.serializer(SubItem.serializer()))

It will crash at runtime with SerializerAlreadyRegistered.

@monxalo
No!
image

I am trying to create a serializer. Can you help

class ArrayListSerializer<T>(
    private val serializer: KSerializer<T>
) : KSerializer<ArrayList<T>> {

    override val descriptor: SerialDescriptor = serializer.descriptor

    override fun serialize(encoder: Encoder, value: ArrayList<T>) {
        encoder.encodeSerializableValue<ArrayList<T>>(ListSerializer<T>(serializer), value)
    }

    override fun deserialize(decoder: Decoder): ArrayList<T> {
        return decoder.decodeSerializableValue<List<T>>(ListSerializer<T>(serializer)).toList() as ArrayList<T>
    }
}

serializersModule = SerializersModule {
    //contextual(ApiResponse.serializer(StoreProductDao.Entity.serializer()))
    contextual(ApiResponse.serializer(ArrayListSerializer(StoreProductDao.Entity.serializer())))
}

But doesn't work

Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlinx.serialization.internal.LinkedHashMapSerializer.collectionSize, parameter $this$collectionSize

I have a class like this
image
The data is always different. Or object or array of objects or primitive.
I am trying to implement something that works well with Gson. But with this everything is complicated :(

Drawback of this approach is that SerializersModule only allows one serializer for each type. So you if do:

contextual(PagedData.serializer(Item.serializer()))
contextual(PagedData.serializer(SubItem.serializer()))

It will crash at runtime with SerializerAlreadyRegistered.

How can this problem be solved? Any other way? I'm already tired of looking for answers (

I did not find any other way, i ended up using ApplicationCall extensions like so:

suspend inline fun <reified T> ApplicationCall.respondWithPaged(message: PagedData<T>) {
    response.pipeline.execute(this, Json.encodeToString(message))
}

@monxalo an improvement in order to respond with the proper content type (JSON)

suspend inline fun <reified T> ApplicationCall.respondWithPaged(message: PagedData<T>) {
    val encoded = Json.encodeToString(message)
    val status = HttpStatusCode.OK
    response.pipeline.execute(this, TextContent(encoded, ContentType.Application.Json, status))
}
Was this page helpful?
0 / 5 - 0 ratings