In my Android app using Retrofit, I am trying to deserialize JSON that has an outer object wrapping a list of items. I am using the GsonConverterFactory with the Retrofit instance to deserialize JSON. I created a custom deserializer to extract only the list of items from the response so I don't have to create the parent wrapper class. I've done this previously with Java but I am not able to get it working with Kotlin. When calling the ItemsService to getItems, I am getting the following exception: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
Is there an issue with my deserializer or how I am configuring it with Gson and Retrofit? Is there something else I am doing wrong?
JSON:
{
"items" : [
{
"id" : "item1"
},
{
"id" : "item2"
},
{
"id" : "item3"
}
}
Deserializer:
class ItemsDeserializer : JsonDeserializer<List<Item>> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): List<Item> {
val items: JsonElement = json!!.asJsonObject.get("items")
val listType= object : TypeToken<List<Item>>() {}.type
return Gson().fromJson(items, listType)
}
}
Item:
data class Item (val id: String)
ItemsService:
interface ItemsService {
@GET("items")
suspend fun getItems(): List<Item>
}
ServiceFactory:
object ServiceFactory {
private const val BASE_URL = "https://some.api.com"
private val gson = GsonBuilder()
.registerTypeAdapter(object : TypeToken<List<Item>>() {}.type, ItemsDeserializer())
.create()
fun retrofit(): Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val itemsService: ItemsService = retrofit().create(ItemsService::class.java)
}
Oh, it's a very common mistake. You have to create parameterized types with TypeToken.getParameterized
. So you have to change object : TypeToken<List<Item>>() {}.type
to TypeToken.getParameterized(List::class.java, Item::class.java).type
class ItemsDeserializer : JsonDeserializer<List<Item>> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): List<Item> {
val items: JsonElement = json!!.asJsonObject.get("items")
val listType= TypeToken.getParameterized(List::class.java, Item::class.java).type
return Gson().fromJson(items, listType)
}
}
private val gson = GsonBuilder()
.registerTypeAdapter(TypeToken.getParameterized(List::class.java, Item::class.java).type, ItemsDeserializer())
.create()