@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
。
我在我的应用程序中使用了 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
。