Search code examples
androidkotlingsonretrofit2

Get an array within parent JSON object with Retrofit in Kotlin


I'm trying the build an app where users can see a list of different plants around the globe. For this I am trying to use Retrofit2 and GSon to get my response from the Perenual Plant Doc API. This is what my response looks looks like:

{
  "data": [
    {
      "id": 1,
      "common_name": "European Silver Fir",
      "scientific_name": [
        "Abies alba"
      ],
      "other_name": [
        "Common Silver Fir"
      ],
      "cycle": "Perennial",
      "watering": "Frequent",
      "sunlight": [
        "full sun"
      ],
      "default_image": {
        "license": 45,
        "license_name": "Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)",
        "license_url": "https://creativecommons.org/licenses/by-sa/3.0/deed.en",
        "original_url": "https://perenual.com/storage/species_image/1_abies_alba/og/1536px-Abies_alba_SkalitC3A9.jpg",
        "regular_url": "https://perenual.com/storage/species_image/1_abies_alba/regular/1536px-Abies_alba_SkalitC3A9.jpg",
        "medium_url": "https://perenual.com/storage/species_image/1_abies_alba/medium/1536px-Abies_alba_SkalitC3A9.jpg",
        "small_url": "https://perenual.com/storage/species_image/1_abies_alba/small/1536px-Abies_alba_SkalitC3A9.jpg",
        "thumbnail": "https://perenual.com/storage/species_image/1_abies_alba/thumbnail/1536px-Abies_alba_SkalitC3A9.jpg"
      }
    },
    {
      "id": 2,
      "common_name": "Pyramidalis Silver Fir",
      "scientific_name": [
        "Abies alba 'Pyramidalis'"
      ],
      "other_name": [],
      "cycle": "Perennial",
      "watering": "Average",
      "sunlight": [
        "full sun"
      ],
      "default_image": {
        "license": 5,
        "license_name": "Attribution-ShareAlike License",
        "license_url": "https://creativecommons.org/licenses/by-sa/2.0/",
        "original_url": "https://perenual.com/storage/species_image/2_abies_alba_pyramidalis/og/49255769768_df55596553_b.jpg",
        "regular_url": "https://perenual.com/storage/species_image/2_abies_alba_pyramidalis/regular/49255769768_df55596553_b.jpg",
        "medium_url": "https://perenual.com/storage/species_image/2_abies_alba_pyramidalis/medium/49255769768_df55596553_b.jpg",
        "small_url": "https://perenual.com/storage/species_image/2_abies_alba_pyramidalis/small/49255769768_df55596553_b.jpg",
        "thumbnail": "https://perenual.com/storage/species_image/2_abies_alba_pyramidalis/thumbnail/49255769768_df55596553_b.jpg"
      }
    }
  ],
  "to": 30,
  "per_page": 30,
  "current_page": 1,
  "from": 1,
  "last_page": 337,
  "total": 10102
}

Based on this response, there are two plants returned. When trying to get the data as if the response was just an array, I get this error: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $

Here are my classes used for this entire ordeal: SpeciesListPlant.kt

data class SpeciesListPlant(
    @SerializedName("common_name")
    val commonName: String,
    @SerializedName("cycle")
    val cycle: String,
    @SerializedName("default_image")
    val defaultImage: DefaultImage,
    @SerializedName("id")
    val id: Int,
    @SerializedName("other_name")
    val otherName: List<String>,
    @SerializedName("scientific_name")
    val scientificName: List<String>,
    @SerializedName("sunlight")
    val sunlight: List<String>,
    @SerializedName("watering")
    val watering: String
)

SpeciesListInterface.kt

interface SpeciesListInterface {
    @GET(ApiConstants.SPECIES_LIST_ENDPOINT_PERENUAL)
    fun getSpeciesList(): Call<List<SpeciesListPlant>>
}

And this is how I get my data with retrofit

val retrofitBuilder = Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(ApiConstants.BASE_URL_PERENUAL)
                .build()
                .create(SpeciesListInterface::class.java)

            val retrofitData = retrofitBuilder.getSpeciesList()

            retrofitData.enqueue(object : Callback<List<SpeciesListPlant>?> {
                override fun onResponse(call: Call<List<SpeciesListPlant>?>, response: Response<List<SpeciesListPlant>?>) {
                    val responseBody = response.body()!!
                    Log.e("Retrofit data", responseBody.toString())
                }

                override fun onFailure(call: Call<List<SpeciesListPlant>?>, t: Throwable) {
                    t.printStackTrace()
                }
            })

I assume that I get this error because the data I want is nested within another object called "data" and not just nested within an array like [plant1, plant2]. How can I just get the array within this "data" field and will this solve this error?

Long story short, how can I get the array paired with the parent "data" field when I get the response.


Solution

  • The easiest solution would be to add a new enclosing data class which has this data property:

    data class SpeciesListResponse(
        @SerializedName("data")
        val data: List<SpeciesListPlant>
    )
    

    And then change the return type of your getSpeciesList() function to use that data class instead:

    fun getSpeciesList(): Call<SpeciesListResponse>