Ktor: KotlinxSerializer unnable to serialize JsonElement/Map<String, *>

Created on 23 Dec 2019  路  1Comment  路  Source: ktorio/ktor

Ktor Version and Engine Used (client or server and name)
kotlin: 1.3.61
kotlinx-serialization-runtime: 0.14.0
ktor-client: 1.3.0-rc

Describe the bug
Setup HttpClient with JsonFeature and serializer KotlinxSerializer.
When running following request:

import kotlinx.serialization.json.json

httpClient.post<String>("http://example.com") {
    contentType(ContentType.Application.Json)
    body = json {
        "key1" to "abc"
        "map" to json {
            "key2" to "abc"
        }
    }
}

got following exception:

java.lang.ClassCastException: kotlinx.serialization.json.JsonObject cannot be cast to kotlinx.serialization.json.JsonLiteral

    at kotlinx.serialization.json.JsonLiteralSerializer.serialize(JsonElementSerializer.kt:103)
    at kotlinx.serialization.json.internal.StreamingJsonOutput.encodeSerializableValue(StreamingJsonOutput.kt:228)
    at kotlinx.serialization.ElementValueEncoder.encodeSerializableElement(ElementWise.kt:72)
    at kotlinx.serialization.internal.MapLikeSerializer.serialize(CollectionSerializers.kt:134)
    at kotlinx.serialization.json.internal.StreamingJsonOutput.encodeSerializableValue(StreamingJsonOutput.kt:228)
    at kotlinx.serialization.CoreKt.encode(Core.kt:74)
    at kotlinx.serialization.json.Json.stringify(Json.kt:95)
    at io.ktor.client.features.json.serializer.KotlinxSerializer.write(KotlinxSerializer.kt:63)
    at io.ktor.client.features.json.JsonFeature$Feature$install$1.invokeSuspend(JsonFeature.kt:101)
    at io.ktor.client.features.json.JsonFeature$Feature$install$1.invoke(JsonFeature.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:273)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:141)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceedWith(PipelineContext.kt:151)
    at io.ktor.client.features.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:87)
    at io.ktor.client.features.HttpCallValidator$Companion$install$1.invoke(HttpCallValidator.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:273)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:141)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:161)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
    at io.ktor.client.HttpClient.execute(HttpClient.kt:155)
    at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:99)

Moreover if we try to use map with with different value types this also leads to exception:

httpClient.post<String>("http://example.com") {
    contentType(ContentType.Application.Json)
    body = mapOf(
        "key1" to "abc",
        "map" to mapOf(
            "key2" to "abc"
        )
    )
}

fails with:

java.lang.ClassCastException: java.util.Collections$SingletonMap cannot be cast to java.lang.String

    at kotlinx.serialization.internal.StringSerializer.serialize(Primitives.kt:97)
    at kotlinx.serialization.json.internal.StreamingJsonOutput.encodeSerializableValue(StreamingJsonOutput.kt:228)
    at kotlinx.serialization.ElementValueEncoder.encodeSerializableElement(ElementWise.kt:72)
    at kotlinx.serialization.internal.MapLikeSerializer.serialize(CollectionSerializers.kt:134)
    at kotlinx.serialization.json.internal.StreamingJsonOutput.encodeSerializableValue(StreamingJsonOutput.kt:228)
    at kotlinx.serialization.CoreKt.encode(Core.kt:74)
    at kotlinx.serialization.json.Json.stringify(Json.kt:95)
    at io.ktor.client.features.json.serializer.KotlinxSerializer.write(KotlinxSerializer.kt:63)
    at io.ktor.client.features.json.JsonFeature$Feature$install$1.invokeSuspend(JsonFeature.kt:101)
    at io.ktor.client.features.json.JsonFeature$Feature$install$1.invoke(JsonFeature.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:273)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:141)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceedWith(PipelineContext.kt:151)
    at io.ktor.client.features.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:87)
    at io.ktor.client.features.HttpCallValidator$Companion$install$1.invoke(HttpCallValidator.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:273)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:141)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:161)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
    at io.ktor.client.HttpClient.execute(HttpClient.kt:155)

The root cause is incorrect serializer builded for value here: https://github.com/ktorio/ktor/blob/1.3.0-rc/ktor-client/ktor-client-features/ktor-client-json/ktor-client-serialization/common/src/io/ktor/client/features/json/serializer/KotlinxSerializer.kt#L75
1) You should add special case for kotlinx.serialization.json.JsonElement.
2) When you build serializer for Map/List/Array you can not assume that all values are the same type.

bug

Most helpful comment

This should be fixed in 1.3.0-rc2

>All comments

This should be fixed in 1.3.0-rc2

Was this page helpful?
0 / 5 - 0 ratings

Related issues

diaodou picture diaodou  路  3Comments

shinriyo picture shinriyo  路  4Comments

lamba92 picture lamba92  路  3Comments

baruchn picture baruchn  路  3Comments

seanf picture seanf  路  3Comments