Home > Enterprise >  Can't cast okHTTP response as JSON
Can't cast okHTTP response as JSON

Time:05-06

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');

CodePudding user response:

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.

CodePudding user response:

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