@FormUrlEncoded @Field 枚举不使用自定义 Moshi 适配器

@FormUrlEncoded @Field enum not using a custom Moshi adapter

我在我的应用程序中使用了 Retrofit (v2.9.0) 和 Moshi (v1.11.0)。我尝试以这种方式调用端点:

@FormUrlEncoded
@PATCH("anime/{anime_id}/my_list_status")
fun updateListStatus(
    @Path("anime_id") animeId: Long,
    @Field("num_watched_episodes") nbWatchedEpisodes: Int,
    @Field("score") score: Double,
    @Field("status") watchStatus: WatchStatus,
): Single<MyListStatus>

但是 WatchStatus->Json 转换没有按预期工作。 WatchStatus 是一个简单的枚举 class:

enum class WatchStatus {
    COMPLETED,
    DROPPED,
    ON_HOLD,
    PLAN_TO_WATCH,
    WATCHING,
}

我创建了一个自定义适配器,因为我的应用程序使用大写枚举名称,而后端使用小写名称:

class AnimeMoshiAdapters {

    /* Others adapters */

    @ToJson
    fun watchStatusToJson(watchStatus: WatchStatus): String = 
watchStatus.toString().toLowerCase(Locale.getDefault())

    @FromJson
    fun watchStatusFromJson(watchStatus: String): WatchStatus =
        WatchStatus.valueOf(watchStatus.toUpperCase(Locale.getDefault()))
}

我这样创建我的 Moshi 实例:

Moshi.Builder()
        .addLast(KotlinJsonAdapterFactory())
        .add(AnimeMoshiAdapters())
        .build()

我的 Retrofit 实例使用它(通过 Koin 注入):

Retrofit.Builder()
        .baseUrl(get<String>(named("baseUrl")))
        .client(get(named("default")))
        .addConverterFactory(MoshiConverterFactory.create(get()))
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build()

解析 Json 以创建 WatchStatus 枚举时会使用适配器。这是值得注意的,因为如果我删除我的自定义适配器,调用将失败并出现错误“com.squareup.moshi.JsonDataException:预期 [COMPLETED,DROPPED,ON_HOLD,PLAN_TO_WATCH,WATCHING] 之一”。 当我尝试调用上面指定的端点时,Json 中 WatchStatus 的转换是错误的,枚举名称保持大写,这意味着我的自定义适配器未被使用。如果我检查 Retrofit 日志,我可以看到它发送“num_watched_episodes=12&score=6.0&status=ON_HOLD”,因此状态未转换为小写。

如果我尝试使用相同的 Moshi 实例手动转换 Json 中的 WatchStatus,它会按预期工作,所以我相信我的自定义适配器实现是正确的。

如何让 Retrofit 在此调用中使用我的自定义 Moshi 适配器?

Moshi 适配器的 toJson 适用于改造请求的主体(== 参数 @BODY 注释),而不是查询参数(== 参数 @FIELD 注释)。这是正确的和预期的行为,因为查询参数按照标准不应该是 JSON 格式的字符串(尽管在您的情况下它只是一个字符串)。如果您的服务器需要 status 字段作为查询参数,那么除了您自己提供小写外别无他法:

@FormUrlEncoded
@PATCH("anime/{anime_id}/my_list_status")
fun updateListStatus(
    ...
    @Field("status") watchStatus: String
): Single<MyListStatus>

并为您的 updateListStatus 提供已经小写的值:

updateListStatus(..., COMPLETED.name.toLowerCase())

如果您对服务器的实现没有影响,则跳过此部分的其余部分post。

如果您想使用自定义适配器的 toJson 功能,您的服务器需要更改请求以接受 JSON 主体而不是查询参数,这样说:

PUT: anime/{anime_id}/my_list_status
BODY:
{
  "anime_id" : Long,
  "num_watched_episodes" : Int,
  "score" : Double,
  "status" : WatchStatus
}

然后你会为正文创建一个数据 class,比如命名为 RequestBody,然后你可以将你的请求更改为:

@PUT("anime/{anime_id}/my_list_status")
fun updateListStatus(
    ...
    @BODY body: RequestBody
): Single<MyListStatus>

在这种情况下,您的自定义适配器将生效并通过其定义的 toJson 逻辑转换 RequestBody 内部的 WatchStatus