Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unwrapping envelopes using Kotlin Serialization? #4219

Open
robpridham-bbc opened this issue Sep 2, 2024 · 0 comments
Open

Unwrapping envelopes using Kotlin Serialization? #4219

robpridham-bbc opened this issue Sep 2, 2024 · 0 comments

Comments

@robpridham-bbc
Copy link

Hi. For some time now, we've been using Retrofit with Moshi, and we used a Converter to unwrap the usual 'envelope', like this:

@JsonClass(generateAdapter = true)
data class Envelope<T>(val data: T)

...

object EnvelopeConverter : Factory() {

    override fun responseBodyConverter(
        type: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *>? {

        val envelopedType = Types.newParameterizedType(Envelope::class.java, type)
        val delegate: Converter<ResponseBody, Envelope<Any>>? =
            retrofit.nextResponseBodyConverter(this, envelopedType, annotations)

        return Unwrapper(delegate)
    }

    private class Unwrapper<T>(
        private val delegate: Converter<ResponseBody, Envelope<T>>?
    ) : Converter<ResponseBody, T> {

        override fun convert(value: ResponseBody): T? {
            return delegate?.convert(value)?.data
        }
    }
}

I imagine you're familiar with this - a fairly common pattern to improve the usability of responses, and in fact a long time ago Jake shared a Gson version of the same thing in a presentation.

We have explored replicating this under Kotlin Serialization and we cannot determine a way forward. The direct equivalent seems to be something like:

@Keep
@Serializable
data class Envelope<T>(val data: T)

...

object EnvelopeConverter : Factory() {

    @OptIn(ExperimentalStdlibApi::class)
    override fun responseBodyConverter(
        type: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *>? {
        val envelopeContentsType = KTypeProjection.invariant(type::class.starProjectedType)
        val envelopedType = Envelope::class.createType(listOf(envelopeContentsType)).javaType
        val delegate: Converter<ResponseBody, Envelope<Any>>? =
            retrofit.nextResponseBodyConverter(this, envelopedType, annotations)

        return Unwrapper(delegate)
    }

    private class Unwrapper<T>(
        private val delegate: Converter<ResponseBody, Envelope<T>>?
    ) : Converter<ResponseBody, T> {

        override fun convert(value: ResponseBody): T? {
            return delegate?.convert(value)?.data
        }
    }
}

This fails because Envelope<Any> is (reasonably) not understood by Kotlin Serialization:

kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.

I might be missing something but I can't see that we can be more specific with the class when only supplied with type as a method parameter.

I should add that in our case, it is important that we configure Retrofit centrally and make it available to decentralised modules to parse their own data objects. We therefore cannot centrally define all the possible polymorphic types and have the resultant Kotlin Serialization polymorphic parser make sense of the type for us.

I think therefore this would fall to Retrofit to support. Have you got any opinions or advice please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant