Can't cast okHTTP response as JSON

Viewed 604

I am using okhttp 4.9.0 to make API requests, but seems that can't get the response body as JSONOBJECT. This is my code:

client.newCall(request).enqueue(object : Callback {

            override fun onFailure(call: Call, e: IOException) {
                Log.d("respuesta","fallida")
            }
            override fun onResponse(call: Call, response: Response)
            {
                val codigoRespuesta: Int = response.code
                if(codigoRespuesta == 401) //Quiere decir que hubo un error al autentificar
                {
                    pantalla.cerrarSesion("Auth error")
                }
                Log.d("Response", response.body!!.string())
                val respuesta: JSONObject = JSONObject(response.body?.string())

                pantalla.procesarResultado(respuesta)
            }
        })

I get the following error:

E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher

    Process: com.ximhai.vekcheckin, PID: 22096
    java.lang.IllegalStateException: closed
        at okio.RealBufferedSource.select(RealBufferedSource.kt:218)
        at okhttp3.internal.Util.readBomAsCharset(Util.kt:258)
        at okhttp3.ResponseBody.string(ResponseBody.kt:187)
        at com.ximhai.vekcheckin.apiRetro$revisarBoleto$1.onResponse(apiRetro.kt:79)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)

On the debug "Log.d("Response", response.body!!.string())" I get:

D/Response: {"resultado":0,"error":"Ticket not found"}

Which means that the API call is actually responding a JSON string.

Now, if I copy the response string and hard code it in the JSON Object like:

val respuesta: JSONObject = JSONObject("{\"resultado\":0,\"error\":\"Ticket not found\"}")

The code works perfectly. (backslashes added automatically by Android Studio when pasting the string).

I have tried:

val respuesta: JSONObject = JSONObject(response.body!!.string()) -> Same result

val respuesta: JSONObject = JSONObject(response.body?.toString()) -> Same result

val respuesta: JSONObject = JSONObject(response.body!!.toString()) -> Same result

val respuesta: JSONObject = JSONObject(response.body().string()) -> Build error: Using 'body(): ResponseBody?' is an error. moved to val

As a piece of extra information: The API is supposed to be responding with Header: Content-type -> application/json

    $newResponse = $response->withHeader('Content-type', 'application/json');
2 Answers

Could it be that when logging the response you're exhausting the response object?

i.e. - comment out Log.d("Response", response.body!!.string()) and see if anything changes.

Why don't you use Retrofit with okhttp3? With retrofit is so much easier to debug. See Why to use Retrofit?

First, you should create an interface for your API:

import okhttp3.ResponseBody
import retrofit2.http.*

interface myAPI{

// some examples

// request: url/api/authenticate?id=42
@GET("authenticate")
    suspend fun connexion(@Query("id") idUser: Int,): ObjectResponse 

// request: url/api/preparation/uploadImage?id=1
@POST("preparation/uploadImage")
    suspend fun uploadImage(@Query("id") idPrep: Int,
                            @Body image: String,): ResponseBody


}

Then you create a remote data provider:

class RemoteDataProvider(context: Context) {
   private var URL = "www.yourapi.com" 

   private val interceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
        this.level = HttpLoggingInterceptor.Level.BODY
    }

    private val client: OkHttpClient = OkHttpClient.Builder().apply {
        this.addInterceptor(interceptor)
    }.build()

    // retrofit creation --> main-safe APIs
    private val retrofit = Retrofit.Builder()
            .baseUrl(URL)
            .client(client)
            .addConverterFactory(ScalarsConverterFactory.create()) // string
            .addConverterFactory(GsonConverterFactory.create()) //Converters can be added to support other types in body
            .build()

    private val service: yourAPI = retrofit.create(yourAPI::class.java)

    // connexion
    suspend fun connexion(id: Int,): Object{
        return service.connexion(id).myObject // call to API interface
    }

    // a [BufferedSource] which contains the [String] of the [ResponseBody].
    suspend fun uploadImage(id: Int, image: String,): BufferedSource{
        return service.uploadImage(id, image).source()
    }
}

Then, you create your data repository:

class DataRepository(private val remoteDataSource: RemoteDataProvider){

    companion object {
        private var TAG = "DataRepository"

        fun newInstance(context: Context): DataRepository {
            return DataRepository(
                    remoteDataSource = RemoteDataProvider(context)
            )
        }
    }

    // connexion
    suspend fun connexion(id: Int,): MyObject{
        return remoteDataSource.connexion(id) // object json {user: ...}
    }

    suspend fun uploadImage(id: Int, image: String,): String{
        return remoteDataSource.uploadImage(idPrep, image).toString() // {"response": ...}
    }

Finally you make the call to the data repository (from your ViewModel preferably):

private val dataRepository by lazy { DataRepository.newInstance(application) }
dataRepository.connexion(id)
Related