Using Gson and Retrofit 2 to deserialize complex API responses

后端 未结 4 1789
予麋鹿
予麋鹿 2020-12-08 05:38

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

4条回答
  •  离开以前
    2020-12-08 06:30

    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.

提交回复
热议问题