Search code examples
androidkotlinretrofit2rx-javamoshi

Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $ - Retrofit2 with RxJava handling JSONArray response


I was able to make it work with a JSON object response but when I tried a JSON Array response I am having an error

Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $

Lets say I am using this JSON Array

[
  {
    "id": 272846,
    "date": "2021-04-04T15:26:48",
    "link": "https://sample.com/crypto-that-surged-by-1250-in-2021-teams-up-with-cardano/",
    "title": {
      "rendered": "sample"
    },
    "content": {
      "rendered": "sample",
      "protected": false
    },
    "author": 52,
    "jetpack_featured_media_url": "https://sample.com/wp-content/uploads/2021/01/Untitled-design-3-1.jpg"
  },
  {
    "id": 272841,
    "date": "2021-04-04T11:03:54",
    "link": "https://sample.com/global-financial-services-company-btig-is-bullish-on-bitcoin-and-microstrategy/",
    "title": {
      "rendered": "sample"
    },
    "content": {
      "rendered": "sample",
      "protected": false
    },
    "author": 52,
    "jetpack_featured_media_url": "https://sample.com/wp-content/uploads/2021/04/3-feb-2021-05-1024x576-1.jpg"
  }
]

The generated Kotlin (Codegen) class using this plugin

class NewsItemModels : ArrayList<NewsItemModel>()
@JsonClass(generateAdapter = true)
data class NewsItemModel(
    @Json(name = "author")
    val author: Int,
    @Json(name = "content")
    val content: Content,
    @Json(name = "date")
    val date: String,
    @Json(name = "id")
    val id: Int,
    @Json(name = "jetpack_featured_media_url")
    val jetpackFeaturedMediaUrl: String,
    @Json(name = "link")
    val link: String,
    @Json(name = "title")
    val title: Title
)

With a service like this

 @GET("posts")
    fun getNewsItems(
        @Query("_fields") key: String,
        @Query("per_page") part: String,
        @Query("categories") category: String
    ):
            Observable<NewsItemModels>

Service interface

companion object {

        fun create(baseUrl: String): EndpointServices {

            val retrofit = Retrofit.Builder()
                .addCallAdapterFactory(
                    RxJava2CallAdapterFactory.create()
                )
                .addConverterFactory(
                    MoshiConverterFactory.create()
                )
                .baseUrl(baseUrl)
                .build()

            return retrofit.create(EndpointServices::class.java)

        }

    }

Fetching

fetch.getNewsItems(
            "author,id,title,content,date,link,jetpack_featured_media_url",
            "30",
            categoryId
        ) //Get the first channel id
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                { result ->
                    Log.wtf("WTF", "$result")
                    swipeRefreshLayout.isRefreshing = false
                },
                { error ->
                    Log.wtf("WTF", "${error.message}")
                    swipeRefreshLayout.isRefreshing = false
                }
            )

I was able to work with JSON Object response but not with JSONArray as much as possible I want only one Retrofit.Builder code. What is the missing part here?


Solution

  • You can either do:

        @GET("posts")
        fun getNewsItems(
            @Query("_fields") key: String,
            @Query("per_page") part: String,
            @Query("categories") category: String
        ): Observable<List<NewsItemModel>>
    

    or update your definition of NewsItemModels to:

    typealias NewsItemModels = List<NewsItemModel>
    

    The error message:

    Expected BEGIN_OBJECT but was BEGIN_ARRAY at path $

    comes from Gson, and it's telling you that based on the way you defined the getNewsItems it's expecting an object (you defined NewsItemModels as a class) but it's getting an array.

    In your example JSON the received payload it's an array so you need to update the return type to either inline the List or use a typealias.