Got a scenario using ktor client where backend can...
# ktor
s
Got a scenario using ktor client where backend can respond either with one response or another response, with nothing common between those. I am trying to see how to properly get the right body out of the
HttpResponse
. • I can not do
this.body<MyTypeHere>
, since the backend is not also using
kotlinx.serialization
in order to give me a
classDiscriminator
so that I can just do a sealed class. • I could make one type with everything nullable so that I can get the body, and then try and manually check what is null and what is not so that I can map it to my own class • Something else? I see that calling
body
twice isn't allowed either, otherwise I would've tried to map it to one, and if that failed I'd try the other.
h
You can always get the body as text and deserialize/check it manually.
s
That would the mean I read the body once, and then try to deserialize to one type, if that fails try the other one. That sounds reasonable I suppose, just need to do bodyAsText and pass that over to kotlinx.serialization afterwards. Is there anything I should be wary of with bodyAsText and it potentially altering the response in some way that would make deserialization not work afterwards, like escaping special characters or anything like that?
a
The response body is saved into a ByteArray by default if the body reading isn't streamed, so you can call the
body
and the
bodyAsText
methods multiple times.
thank you color 1
s
Thank you both! I think I got this now 😊
I can kinda see that
body
uses the serializer passed into the ktor client itself in order to do the serialization/deserialization. Is there a way for me to take that out of the
HttpResponse
so that I don't need to pass my
Json
instance down to where I need to do this deserialization?
a
Unfortunately, there is no way accessing the converter from the
ContentNegotiation
plugin.
s
Ah I can also get access to the
bodyNullable
if I call
call.bodyNullable
, it was just not available on
HttpResponse
itself. I think I will just use that and if it's null try the next thing instead. This way I just use the ktor pipeline itself without having to extract the serializer or anything
bodyNullable
actually throws a
JsonConvertException
(which is
ContentConvertException
:
Exception
) when it does not find the right type actually, instead of just returning null ☠️ Ouch. And the docs for
bodyNullable
mention that it might throw
NoTransformationFoundException
(which is
UnsupportedOperationException : RuntimeException
) or
DoubleReceiveException
, so I am guessing the docs above it are wrong and should mention that too? What I am doing now which works but I really do not love 🤷 is this
Copy code
internal suspend fun HttpResponse.foo(): FooResult {
    val success = try {
        body<Success>()
    } catch (e: JsonConvertException) {
        null
    }
    if (success != null) {
        return SuccessResult
    }
    val failure = try {
        body<Failure>()
    } catch (e: JsonConvertException) {
        null
    }
    if (failure != null) {
        return FooResult.TypedResponse(failure)
    }
    return FooResult.OtherFailure
}
c
I think a JsonContentPolymorphicSerializer would solve your issue, a lot simpler for you. Thats what I've used in the past when I have a response that will return different data types depending on some condition
s
Aha, that's a new one for me 👀 Looks interesting actually, but I think in that case I'd still need to pass down my original Json configuration that I've previously passed to my ktor client, like in this part:
Copy code
install(ContentNegotiation) {
  json(Json { isLenient = true; ignoreUnknownKeys = true; ... })
}
so that I can do
MyJsonConfig.decodeFromString...
right? But if I do that, then I could totally use this polymorphic serializer, and then only have to wrap this in one try catch in case the response object can not map to either of my 2 responses.
c
not sure im getting you here, don't see the need to pass your json cofiguration to your client at the call site, but let me spit up a gist of what I would do
@Stylianos Gakis quick rough and ready example here
theres no need to call
decodeFromString
when using the serializer
s
Sorry, I worded it a bit weirdly. As the docs show of the link you showed me, you gotta at some point do
Json.decodeFromString(PaymentSerializer, bodyAsText())
.
Now that
Json
there is not something that is going to be configured with the exact same configuration as the Json that I am providing to my ktor ContentNegotiation, and optimally I would not want to have two different ways of doing deserialization or serialization
Oh right! I can just pass that Polymorphic Serializer to my sealed interface itself, and then let it automatically happen in the
body<>()
call, since that one would then use the right Json configuration, right?
c
correct
s
That's looking much better than my solution, I will give this a shot, thanks a ton!
🙌🏼 1
c
you can then
when
over the sealed class, that returns, to handle each case of possible response
🌟 1
I did something similar, in a case where an API returned a list of objects and each object could be one of 10 different data types. We were displaying these in a list, like a video component, image component, social post component etc...
s
Yeap, sounds like the right way to approach that tbh. I was a bit lost with all this since we use GraphQL for everything else, and whenever I need to do something other than GQL I am always so confused to not have all the schema and auto generated types ready for me to use 😄
🙌🏼 1
c
ha yeah GQL is great for these kind of things! Glad I was able to help 👌🏼
🙏 1