LoginSignup
1
1

More than 1 year has passed since last update.

retrofit2とmoshiとenum

Posted at

若干詰まったので、備忘録

やりたかったこと

retrofit+moshiで、APIのレスポンス(json)をenumとしてデシリアライズして返してほしい

例:月の和名を返すAPI

image.png

アプリ側から月(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
SHIWASU 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かのどちらかが必要。
今回はデシリアライズのみなので、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を文字列として受け取る場合」を実施するとエラーになりました:sleepy:

E/TEST: Expected an int but was KISARAGI at path $.month_jpn

どちらでもデシリアライズしてくれるやり方を募集しています。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1