Search code examples
androidkotlingsonretrofit2

Gson ignores custom @SerializedName values for enum on serialization


I have enums like this:

enum class NewTaskState(val value: Int) {

    @SerializedName("0")
    STATE_INSTALLED(0),

    @SerializedName("1")
    STATE_LAUNCHED(1)

}

API call with retrofit:

 @POST("tasks/android/{id}/state/update/")
 @FormUrlEncoded
 fun updateAppTaskState(
     @Path("id") id: Long,
     @Query("device_pk") deviceId: Long,
     @Field("new_state") newState: NewTaskState
 ): Single<Response<Any>>

And GSON configuration:

return GsonBuilder()
    .registerTypeAdapter(Date::class.java, DateDeserializer())
    .enableComplexMapKeySerialization()
    .create()

Gson version is 2.8.5.

When I use this call, I see in logs:

I/OkHttp: --> POST <removed>/tasks/android/1/state/update/?device_pk=8
I/OkHttp: Content-Type: application/x-www-form-urlencoded
I/OkHttp: Content-Length: 25
I/OkHttp: Authorization: Token <removed>
I/OkHttp: new_state=STATE_INSTALLED

So it ignores @SerializedName value on serialization, but works fine on deserialization.

What's wrong?


Solution

  • This happens because Gson does not participate in url form-encoding.

    From @Field's javadoc:

    Values are converted to strings using Retrofit.stringConverter(Type, Annotation[]) (orObject.toString(), if no matching string converter is installed) and then form URL encoded. null values are ignored. Passing a List or array will result in a field pair for each non-null item.

    And GsonConverterFactory does not override Converter.Factory#stringConverter so it is not ignoring anything. It's merely not kicking in


    As a workaround you can do something similar:

    Converter:

    object EnumAsOrdinalToStringConverter : Converter<Enum<*>, String> {
        override fun convert(value: Enum<*>): String =
                value.ordinal.toString()
    }
    

    Factory:

    class EnumAsOrdinalToStringConverterFactory : Converter.Factory() {
        override fun stringConverter(
                type: Type,
                annotations: Array<Annotation>,
                retrofit: Retrofit
        ): Converter<*, String>? = if (type is Class<*> && type.isEnum) {
            EnumAsOrdinalToStringConverter
        } else {
            null
        }
    }
    

    Building Retrofit:

    addConverterFactory(EnumAsOrdinalToStringConverterFactory())