Search code examples
jsonkotlingsonretrofit

How to match data class with JSON response from API in Retrofit?


data class Item(
    val calories: Int,
    val carbohydrates_total_g: Double,
    val cholesterol_mg: Int,
    val fat_saturated_g: Int,
    val fat_total_g: Double,
    val fiber_g: Double,
    val name: String,
    val potassium_mg: Int,
    val protein_g: Double,
    val serving_size_g: Int,
    val sodium_mg: Int,
    val sugar_g: Double
)

data class nutritionData(
    val items: List<Item>
)

val client = OkHttpClient.Builder()
.addInterceptor { chain ->
    val request = chain.request()
        .newBuilder()
        .addHeader("X-Api-Key", ApiHeaders.API_KEY)
        .build()
    chain.proceed(request)
}
.build()

interface NinjaAPI {

    @GET("v1/nutrition?")
    fun getNutrition(@Query("query") foodName: String): Call<List<nutritionData>>
}

    const val BASE_URL = "https://api.calorieninjas.com/"
fun getMyNutrition(foodName: String) {

    val retrofitBuilder = Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(BASE_URL)
        .client(client)
        .build()
        .create(NinjaAPI::class.java)

    val retrofitData = retrofitBuilder.getNutrition(foodName)
    retrofitData.enqueue(object : Callback<List<nutritionData>?> {
        override fun onResponse(
            call: Call<List<nutritionData>?>,
            response: Response<List<nutritionData>?>
        ) {
            Log.d(TAG, "IT WORKED")
        }

        override fun onFailure(call: Call<List<nutritionData>?>, t: Throwable) {
            Log.d(TAG, "onFailure: " + t.message)
        }
    })
}

I tried everything to display the (calories, fats, protein, and carbs) from CaloriesNinja API but I couldn't, I tried to check the catlog for info and turns out onResponse never occurred and all the button clicks go to onFailure and produce:

onFailure: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $

I Googled that and it is properly my formatting to the data from the API but I copied the response example they have and used the plugin to convert it to kotlin class.


Solution

  • You are specifying Call<List<nutritionData>> as return type of the API call, that means the JSON data would have to look like this:

    [
      {
        "items": [ ... ]
      },
      {
        "items": [ ... ]
      },
      ...
    ]
    

    However, the JSON data is actually only a single JSON object which directly represents your nutritionData:

    {
      "items": [ ... ]
    }
    

    Therefore you should change Call<List<nutritionData>> to Call<nutritionData>.

    This is also what the Gson exception message is saying: You specified that the type is List<...> so Gson expects a JSON array, but the JSON data actually starts with a JSON object. And the location information "line 1 column 2 path $" of the exception points you to that error location in the JSON data (possibly with some slight inaccuracies).