I\'m using Retrofit 2 and Gson and I\'m having trouble deserializing responses from my API. Here\'s my scenario:
I have a model object named Employee
th
I must say that I haven't thought about using Interceptors
for something like this, but that's an interesting approach. Here's what I usually do when I need to model backend-wrapper responses:
If you get something like this from the backend:
{
"success": "success", // Let's say you may get "error", "unauthorized", etc.
"payload": [...] // Let's say that you may either get a json object or an array.
}
Then you could declare a deserializer:
import com.demo.core.utils.exceptions.NonSuccessfullResponse
import com.google.gson.Gson
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
/**
* A custom deserializers that uses the generic arg TYPE to deserialize on the fly the json responses from
* the API.
*/
class WrapperDeserializer(
private val castClazz: Class,
private val isList: Boolean
) : JsonDeserializer {
val gson = Gson()
override fun deserialize(
element: JsonElement,
arg1: Type,
arg2: JsonDeserializationContext
): TYPE? {
val jsonObject = element.asJsonObject
if (jsonObject.get("success").asBoolean) {
return if (isList) {
val type = TypeToken.getParameterized(List::class.java, castClazz).type
gson.fromJson(jsonObject.get("payload"), type)
} else {
gson.fromJson(jsonObject.get("payload"), castClazz)
}
} else {
throw NonSuccessfullResponse()
}
}
}
And then in whatever place you instantiate your Gson instance you can do something like:
fun provideGson(): Gson {
val bookListType = TypeToken.getParameterized(List::class.java, ApiAvailableBooksResponse::class.java).type
return GsonBuilder()
.registerTypeAdapter(bookListType, WrapperDeserializer(ApiAvailableBooksResponse::class.java, true))
.registerTypeAdapter(ApiProfileInfoResponse::class.java, WrapperDeserializer(ApiProfileInfoResponse::class.java, false))
.registerTypeAdapter(Date::class.java, DateDeserializer())
.create()
}
Notice that there we are mapping two different kinds of responses, a list of books, something like:
{
"success": "success",
"payload": [
{...}, // Book 1
{...}, // Book 2
{...} // Book 3
]
}
And a single user profile response:
{
"success": "success",
"payload": {
"name": "etc",
// ...
}
}
Again, the Interceptor
approach is a very interesting option that I haven't thought about before - it worries me a bit in terms of flexibility since you're forcing all endpoint responses to follow the same standard - but it looks like a more tidy approach.