androidkotlingsonretrofit

Fetching Api returns Array of Arrays instead Array of Objects using Retrofit and Gson


Recently I have been working on project which includes working with API. I have checked it in Postman and I am sure that its response is an Array of Objects but when tried to use it I faced Expected BEGIN_OBJECT but was BEGIN_ARRAY error so I changed the API response type to List to see what happens and I realized instead of Array of objects it returns Arrays of object's arrays and in all of them objects are null and have no valid response.

fun createApiService(): ApiService {

    val client = OkHttpClient.Builder()
        .addInterceptor(
            BasicAuthInterceptor(
                "username",
                "password"
            )
        )
        .build()


    val api = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    return api.create(ApiService::class.java)

}

class BasicAuthInterceptor(username: String, password: String) : Interceptor {
    private var credentials: String = Credentials.basic(username, password)

    override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
        var request = chain.request()
        request = request.newBuilder().header("Authorization", credentials).build()
        return chain.proceed(request)
    }
}

This part is for creating API and giving it username and password because it is private and it needs Authorization:

data class ProductResponse(
    val products: List<Product>
)

data class Product(

     /// its attributes are here

    )
interface ApiService {

    @GET("products")
    suspend fun getAllProducts(): List<ProductResponse>

}
class ProductRepositoryImpl(
    private val apiService: ApiService,
) : ProductRepository {
    override suspend fun getProducts(isInternetConnected: Boolean): List<Product> {
        val dataFromServer = apiService.getAllProducts()
        Log.v("api",dataFromServer.toString())
        return dataFromServer[0].products
    }
}

and Log shows:

[ProductResponse(products=null), ProductResponse(products=null), ProductResponse(products=null), ProductResponse(products=null), ProductResponse(products=null), ProductResponse(products=null), ProductResponse(products=null), ProductResponse(products=null), ProductResponse(products=null), ProductResponse(products=null)]

[{
    "name": "test",
    "type": "simple",
    "featured": false,
    "catalog_visibility": "visible",
    "description": "",
    "short_description": "",
    "price": "351000",
    "regular_price": "351000",
    "sale_price": "",
    "on_sale": false,
    "purchasable": true,
    "total_sales": 0,
    "button_text": "",
    "tax_status": "taxable",
    "tax_class": "",
    "manage_stock": false,
    "stock_quantity": null,
    "backorders": "no",
    "backorders_allowed": false,
    "backordered": false,
    "low_stock_amount": null,
    "sold_individually": false,
    "weight": "",
    "shipping_required": true,
    "shipping_taxable": true,
    "shipping_class": "",
    "shipping_class_id": 0,
    "reviews_allowed": true,
    "average_rating": "0.00",
    "rating_count": 0,
    "parent_id": 0,
    "purchase_note": "",
   
and it continues like this and is more than 600 lines

Solution

  • The return type of your ApiService methods should directly be the desired response type (or it can be wrapped inside a retrofit2.Response, e.g. Response<MyClass>). There is no need for the separate ProductResponse class you have, which just holds the actual response data.

    So since your API returns a JSON array, your service method should have List<...> as return type (or any other type which Gson can deserialize from a JSON array).

    The reason why you are getting a lot of ProductResponse(products=null) is that you have specified List<ProductResponse> as return type. So Gson expects the following JSON structure:

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

    (and because Gson ignores missing fields, ProductResponse#products simply remains null)

    To solve this, change the return type of getAllProducts and delete the ProductResponse class:

    @GET("products")
    suspend fun getAllProducts(): List<Product>