Перечисление @FormUrlEncoded @Field не использует пользовательский адаптер Moshi

Я использую 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:

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: Ожидается одно из [ЗАВЕРШЕНО, УДАЛЕНО, ON_HOLD, PLAN_TO_WATCH, WATCHING]», если я удалю свой собственный адаптер. Когда я пытаюсь вызвать конечную точку, указанную выше, преобразование WatchStatus в Json неверно, и имя перечисления остается в верхнем регистре, что означает, что мой пользовательский адаптер не используется. Если я проверю журналы модернизации, я увижу, что он отправляет «num_watched_episodes=12&score=6.0&status=ON_HOLD», поэтому статус не преобразуется в нижний регистр.

Если я попытаюсь вручную преобразовать WatchStatus в Json, используя тот же экземпляр Moshi, он будет работать, как и ожидалось, поэтому я считаю, что моя реализация пользовательского адаптера верна.

Как я могу заставить Retrofit использовать мой пользовательский адаптер Moshi в этом вызове?

0
0
379
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

toJson адаптеров Moshi применяются к телу запроса на модернизацию (== параметры, аннотированные @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())

Если вы не имеете никакого влияния на реализацию сервера, пропустите оставшуюся часть этого поста.

Если вы хотите использовать функцию 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>

в этом случае ваш пользовательский адаптер вступит в силу и преобразует WatchStatus внутри RequestBody в соответствии с определенной логикой toJson.

Спасибо за объяснение, все понятно. Чтобы сохранить синтаксический анализ json<->WatchStatus в одном месте, я повторно использовал адаптер Moshi в своем репозитории: AnimeMoshiAdapters().watchStatusToJson(listStatus.status), и он работает так, как хотел.

MickaelG 27.12.2020 15:17

Другие вопросы по теме