Skip to content

Unwrapping envelopes using Kotlin Serialization? #4219

Open
@robpridham-bbc

Description

@robpridham-bbc

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions