Search code examples
androidkotlingsonretrofit2json-deserialization

Error deserializing JSON response with custom Gson deserializer


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)
}

Solution

  • 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()