retrofit2와 모시와 enum

약간 막혔으므로 비망록

하고 싶었던 일



retrofit + moshi에서 API 응답 (json)을 enum으로 deserialize하고 반환하십시오.

예: 월의 화명을 반환하는 API





앱 측에서 월(1~12)을 서버에 요청하면 응답으로 월의 화명이 반환되는 연계가 있으며,
앱 측과 서버 측 모두에서 반환 값으로 Enum이 정의되어 있다고 가정합니다.


상수



MUTSUKI
0

KISARAGI
1

YAYOI
2

UDUKI
3

SATSUKI
4

MINADUKI
5

FUMIDUKI
6

HADUKI
7

NAGATUKI
8

KANNADUKI
9

Shimotuki
10

시와수
11

UNKNOWN
12


형식


// リクエスト
{
 "month": 1
}

// レスポンス(名前で返す)
{
 "month_jpn": "MUTSUKI"
}

// レスポンス(序数で返す)
{
 "month_jpn": 0
}

앱 측


// Enumの定義
enum class Wareki {
    // 縦に長いので折り返してます
    MUTSUKI, KISARAGI, YAYOI, UDUKI,
    SATSUKI, MINADUKI, FUMIDUKI, HADUKI,
    NAGATUKI, KANNADUKI, SHIMOTUKI, SHIWASU,
    UNKNOWN
}

// レスポンスを受け取るオブジェクトのクラス
data class ResData(
    @Json(name = "month_jpn") 
    val monthJpn: Wareki
)

// APIをコールする箇所
suspend fun fetchMonthJpn(month: Int) {
    try {
        // ここ
        val res = Api.service.fetchMonthJpn(ReqData(month))
        Log.d("TEST", res.raw().toString())

        val monthJpn = res.body()!!.monthJpn
        monthJpn.let {
            Log.d("TEST", it.name + ":" + it.javaClass)
        }
    } catch (e: JsonDataException) {
        Log.e("TEST", e.message!!)
    }
}

// moshiとretrofit
private val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

private val retrofit = Retrofit.Builder()
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .baseUrl(BASE_URL)
    .build()

1. enum을 캐릭터 라인으로서 받는 경우(이름으로 반환의 경우)



enum을 문자열로 받는 경우는, 특히 궁리 없이 그대로 Enum으로서 디시리얼라이즈 해 준다.
D/TEST: Response{protocol=http/1.1, code=200, message=OK, url=http://10.0.2.2:8000/api/fetch_month_jpn/}
D/TEST: KISARAGI:class jp.co.sample.Wareki

2. enum을 수치로서 받는 경우(서수로 반환의 경우)



서수로 받으면 디시리얼라이즈 할 수 없으며 예외가 발생합니다.
E/TEST: Expected one of [MUTSUKI, KISARAGI, YAYOI, UDUKI, SATSUKI, MINADUKI, FUMIDUKI, HADUKI, NAGATUKI, KANNADUKI, SHIMOTUKI, SHIWASU, UNKNOWN] but was 1 at path $.month_jpn

어댑터 도입



이런 경우에는 어댑터를 도입하면 잘 됐다.

우선 이런 어댑터를 만든다.
class WarekiAdapter {
    @FromJson
    fun fromJson(monthJpn: Int): Wareki {
        return Wareki.values().find { it.ordinal == monthJpn } ?: throw IllegalArgumentException()
    }
}

moshi에 add하는 어댑터에는 FromJson 또는 ToJson이 필요합니다.
이번에는 deserialize 만이므로 FromJson 만 정의하고 json에서받은 값과 해당 enum 값을
find에서 찾고 반환합니다. 발견되지 않는 경우는 예외를 발행하는 것 같은 느낌으로 작성했다.

이것을 moshi에 add한다.
private val moshi = Moshi.Builder()
    .add(WarekiAdapter()) // ここ
    .add(KotlinJsonAdapterFactory())
    .build()

이상.

다시 API를 호출하면 괜찮은 디시리얼라이즈 해준다.
D/TEST: Response{protocol=http/1.1, code=200, message=OK, url=http://10.0.2.2:8000/api/fetch_month_jpn/}
D/TEST: KISARAGI:class jp.co.sample.Wareki

appendix



덧붙여서, 위의 어댑터를 추가 한 상태에서 "1. enum을 문자열로받는 경우"를 수행하면 오류가 발생했습니다.
E/TEST: Expected an int but was KISARAGI at path $.month_jpn

어느 쪽이든 deserialize 해주는 방법을 모집하고 있습니다.

좋은 웹페이지 즐겨찾기