Search code examples
jsonkotlinenumsgsonretrofit2

My retrofit gson deserializer don't see enum


Whenever I'm getting response from the server where JSON looks like that {"rights":[{"name":"stock_management","right":true}]} my retrofit uses class SimpleResponse<T> to convert it to my desired response but instead working as intended I'm getting emptylist as a value for rights.

data class SimpleResponse<T>(
    val data: T? = null,
    val error: String? = null,
    val rights: List<LMSPermission> = emptyList()
)

@Entity
data class LMSPermission(
    @PrimaryKey(autoGenerate = false)
    val name: Permission,
    val right: Boolean = false
) {
    enum class Permission(val lmsName: String) {
        @SerializedName("stock_management")
        STOCK_PERMISSION("stock_management")
    }
}

I tried to just create gson instance and generate classes from that but everything worked fine so i tried to add interceptor to my retrofit instance and there I tried to get class instance from response and it worked.

@Provides
    @Singleton
    fun provideRetrofitDAO(): RetrofitDao {
        val gson = GsonBuilder()
            .registerTypeAdapter(Event::class.java, EventDeserializer())

        val logging = HttpLoggingInterceptor()
            .setLevel(HttpLoggingInterceptor.Level.BODY)
        val httpClient = OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(logging)
            .addInterceptor { chain ->
                val response = chain.proceed(chain.request().newBuilder().build())
                Log.d(TAG, "provideRetrofitDAO: $response")
                response.let {
                    if (it.code == 200)
                        Log.d(TAG, "provideRetrofitDAO: ${gson.create().fromJson<SimpleResponse<Any>>(it.body.string(), TypeToken.getParameterized(SimpleResponse::class.java, Any::class.java).type)}")
                }
                response
            }
        val retrofitBuilder =
            Retrofit
                .Builder()
                .baseUrl(if (MainActivity.debug) API_BASE_URL_DEBUG else API_BASE_URL)
                .addConverterFactory(
                    GsonConverterFactory.create(gson.create())
                )
                .callbackExecutor(Executors.newSingleThreadExecutor())
        val retrofit =
            retrofitBuilder
                .client(
                    httpClient.build()
                )
                .build()

        return retrofit.create(RetrofitDao::class.java)
    }

At this point I'm quite sure that converter factory is at fault but I don't know how to repair that nor how to check if it is really a problem.


Solution

  • It seems that writing my own GsonConverterFactory where I'm using fromJson method instead of adapter from getAdapter was the answer. Don't ask me why this works but here is the code, MyTypeAdapter is not really implemented because I used it as a container for Type variable.

    class MyGsonConverterFactory(val gson: Gson) : Converter.Factory() {
            override fun responseBodyConverter(
                type: Type,
                annotations: Array<out Annotation>,
                retrofit: Retrofit
            ): Converter<ResponseBody, *> {
                val adapter = MyTypeAdapter<Any>(type)
                return GsonResponseBodyConverter(gson, adapter)
            }
    
            override fun requestBodyConverter(
                type: Type,
                parameterAnnotations: Array<Annotation?>,
                methodAnnotations: Array<Annotation?>,
                retrofit: Retrofit
            ): Converter<*, RequestBody> {
                val adapter = gson.getAdapter(TypeToken.get(type))
                return GsonRequestBodyConverter(gson, adapter)
            }
        }
    
        internal class GsonResponseBodyConverter<T>(
            private val gson: Gson,
            private val adapter: TypeAdapter<T>
        ) :
            Converter<ResponseBody, T> {
            @Throws(IOException::class)
            override fun convert(value: ResponseBody): T {
                (adapter as? MyTypeAdapter)?.typeToken!!.let {
                    val test = gson.fromJson<T>(value.string(), it)
                    Log.d(TAG, "convert: $test")
                    return test
                }
            }
        }
    
        internal class GsonRequestBodyConverter<T>(
            private val gson: Gson,
            private val adapter: TypeAdapter<T>
        ) : Converter<T, RequestBody> {
            @Throws(IOException::class)
            override fun convert(value: T): RequestBody {
                val buffer = Buffer()
                val writer: Writer = OutputStreamWriter(buffer.outputStream(), UTF_8)
                val jsonWriter = gson.newJsonWriter(writer)
                adapter.write(jsonWriter, value)
                jsonWriter.close()
                return buffer.readByteString().toRequestBody(MEDIA_TYPE)
            }
    
            companion object {
                private val MEDIA_TYPE: MediaType = "application/json; charset=UTF-8".toMediaType()
                private val UTF_8 = Charset.forName("UTF-8")
            }
        }
    
        internal class MyTypeAdapter<T>(val typeToken: Type) : TypeAdapter<T>() {
            override fun write(out: JsonWriter?, value: T) {}
            override fun read(`in`: JsonReader?): T { TODO() }
        }