Hello! How would I create a `JsonContentPolymorphi...
# serialization
m
Hello! How would I create a
JsonContentPolymorphicSerializer
for the following class hierarchy? I want it to select the proper serializer by checking the value of the "ok" key in the JSON and if it's true, return a
ApiResponse.Success
serializer and if false return the
ApiResponse.Error
serializer.
Copy code
@Serializable
internal sealed class ApiResponse {

    @SerialName("ok") abstract val ok: Boolean

    @Serializable
    class Success<T>(
        override val ok: Boolean,
        @SerialName("result") val result: T
    ) : ApiResponse()

    @Serializable
    class Error(
        override val ok: Boolean,
        @SerialName("error_code") val errorCode: Int,
        @SerialName("description") val description: String
    ) : ApiResponse()
}
e
have you tried
Copy code
class ApiResponseSerializer<T>(
    private val tSerializer: KSerializer<T>,
) : JsonContentPolymorphicSerializer<ApiResponse>(ApiResponse::class) {
    override fun selectDeserializer(content: JsonElement) = when {
        "result" in content.jsonObject -> ApiResponse.Success.serializer(tSerializer)
        else -> ApiResponse.Error.serialier()
    }
}
?
m
Thank you very much for the response, I'm really sorry, I realized I made a small error and I need to have the type parameter on the
ApiResponse
class as well. This is what the updated
ApiResponse
class looks like:
Copy code
@Serializable
internal sealed class ApiResponse<T> {

    @SerialName("ok") abstract val ok: Boolean

    @Serializable
    class Success<T>(
        override val ok: Boolean,
        @SerialName("result") val result: T
    ) : ApiResponse<T>()

    @Serializable
    class Error<T>(
        override val ok: Boolean,
        @SerialName("error_code") val errorCode: Int,
        @SerialName("description") val description: String
    ) : ApiResponse<T>()
}
Now, the code you proposed doesn't work anymore because I can't pass a
KClass<T>
into the constructor of
JsonContentPolymorphicSerializer
.
e
with that structure, you're basically stuck due to https://github.com/Kotlin/kotlinx.serialization/issues/1784
m
Thank you, is my approach a good one though? I technically don't need the type param on the
ApiResponse
class but I wanted to do fancy stuff like this which would be otherwise not possible:
Copy code
when (val apiResponse = response.body<ApiResponse<List<UpdateDto>>>()) {
    is ApiResponse.Success -> return apiResponse.result.map { it.toEntity() }
    is ApiResponse.Error -> error(
        """
            Got an error response from the getUpdates method which should be impossible.
            Error code: ${apiResponse.errorCode}
            Description: ${apiResponse.description}
        """.trimIndent()
    )
}
e
the usual way of writing these classes would be along the lines of
Copy code
sealed class ApiResponse<out T> {
    data class Ok<out T>(val value: T) : ApiResponse<T>()
    data class Err(val message: String) : ApiResponse<Nothing>()
}
but unfortunately that doesn't play well with serialization https://github.com/Kotlin/kotlinx.serialization/issues/614 😞
m
So I'm out of options?
e
I believe your only option is to create a whole serializer without using
JsonContentPolymorphicSerializer
:-/
m
Alright, let's get to work. Thank you, I'll report here if any issues arise.
e
this is simpler than your example but you should be able to extend it easily
m
Damn, thank you, it is working like a charm! I was struggling yesterday so much, I didn't know
kotlinx.serialization
would pass automatically for you an instance of KSerializer<T> into the constructor of my serializer. The more ya know! Here's my final code in case somebody needs it. And thanks again, @ephemient, for your super quick responses.