若干詰まったので、備忘録
やりたかったこと
retrofit+moshiで、APIのレスポンス(json)をenumとしてデシリアライズして返してほしい
例:月の和名を返す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 |
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を文字列として受け取る場合」を実施するとエラーになりました
E/TEST: Expected an int but was KISARAGI at path $.month_jpn
どちらでもデシリアライズしてくれるやり方を募集しています。