retrofit2kotlinx.serialization

How can I parse a bare "null" response from the server with kotlinx.serialization?


I have a retrofit api interface with a method that looks like this:

   @GET("/data")
   suspend fun getData(): DataDto?

The server in this instance is returning a 200, and the json response body is null (bare string, no quotes).

This results in an exception:

java.util.concurrent.ExecutionException: kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Expected start of the object '{', but had 'n' instead at path: $
JSON input: null

I don't think I change the backend at this point. Is there any way to get this to work?

I'm using kotlin 2.0.0, kotlinx.serialization gradle plugin 2.0.0, and kotlinx-serialization-json 1.7.1.

My json config looks like this:

    @OptIn(ExperimentalSerializationApi::class)
    @Singleton
    @Provides
    fun provideJson(): Json = Json {
        allowTrailingComma = true
        encodeDefaults = true
        explicitNulls = false
        ignoreUnknownKeys = true
        prettyPrint = true
        isLenient = true
    }

I've tried with explicitNulls set to both true and false, makes no difference.


Solution

  • After going down several different rabbit holes, I finally looked at the json.asConverterFactory(contentType) source. There's a FormatString class, that has a method like this:

            override fun <T> fromResponseBody(
                loader: DeserializationStrategy<T>,
                body: ResponseBody
            ): T {
                val string = body.string()
                return format.decodeFromString(loader, string)
            }
    

    This takes a string, decodes it, and returns an instance of T - it doesn't allow a "null" response - it always expects an object. It requires copying a fair bit of code, and modifying things to accept a T?, and a very small change to the fromResponseBody method to check the string value for "null" before forwarding it on to the decodeFromString.

    So now I can do json.asNullableConverterFactory(contentType), and it works as intended.