【问题标题】:@FormUrlEncoded @Field enum not using a custom Moshi adapter@FormUrlEncoded @Field 枚举不使用自定义 Moshi 适配器
【发布时间】:2021-04-01 07:22:22
【问题描述】:

我在我的应用程序中使用 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 是一个简单的枚举类:

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: Expected one of [COMPLETED, DROPPED, ON_HOLD, PLAN_TO_WATCH, WATCHING]”。 当我尝试调用上面指定的端点时,Json 中的 WatchStatus 的转换是错误的,并且枚举名称保持大写,这意味着我的自定义适配器未使用。如果我检查改造日志,我可以看到它发送“num_watched_episodes=12&score=6.0&status=ON_HOLD”,因此状态不会转换为小写。

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

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

【问题讨论】:

    标签: android json enums retrofit moshi


    【解决方案1】:

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

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

    并为您的 updateListStatus 提供已经 lowerCased 值:

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

    如果您对服务器的实现没有影响,请跳过本文的其余部分。

    如果您想使用自定义适配器的 toJson 函数,您的服务器需要将请求更改为接受 JSON 正文而不是查询参数,如下所示:

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

    然后您将为主体创建一个数据类,例如命名为RequestBody,然后您可以将您的请求更改为:

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

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

    【讨论】:

    • 感谢您的解释,这完全有道理。为了将 jsonWatchStatus 解析保留在一个地方,我在我的存储库中重用了 Moshi 适配器:AnimeMoshiAdapters().watchStatusToJson(listStatus.status) 并且它可以按需要工作。
    猜你喜欢
    • 1970-01-01
    • 2018-06-17
    • 1970-01-01
    • 2017-02-08
    • 2017-03-09
    • 2017-03-18
    • 2020-02-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多