はじめに
こんにちは、株式会社レコチョク新卒1年目の我那覇です。 昨年10月よりプロダクト開発第1グループに配属され、Androidアプリ開発エンジニアとして業務に携わっています。
Serializationは、KotlinでJSONレスポンスを扱う際に便利なライブラリです。 本記事では私自身の経験を交えながら、使用する際に初心者が気をつけたいポイントを紹介していきます。 これからSerializationを導入しようと考えている方の参考になれば幸いです。
実行環境
macOS 14.4.1 Sonoma Android Studio Koala | 2024.1.1 Patch 1 Kotlin 1.9.0
導入
Serializationを利用するには、
build.gradle.ktsに以下のプラグインの設定を追加します。
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:<バージョン>"
}
この設定により、データクラスに @Serializablecode>@Serializable</codeアノテーションを付与することで、データクラスのシリアライズ/デシリアライズが可能になります。 本記事では詳しい説明は割愛するため、詳細な導入方法や基本的な使い方については公式ドキュメントをご参照ください。
公式ドキュメント GitHub
使用する際のポイント
💡 ポイント1:JSONレスポンスとデータクラスの一致
Serializationを使用する際、JSONレスポンスで返ってくるキー名とデータクラスの変数名を正しく合わせることが基本です。 しかし、以下のような違いが原因でエラーが発生する場合があります。
- JSONのキー名とデータクラスの変数名が異なる(キャメルケース、スネークケースなど)
- 大文字と小文字が異なっている
Kotlinでは変数名にキャメルケース(例:
userName)を使用することが一般的ですが、JSONレスポンスではスネークケース(例:user_name)が使用されていることが多いため、Kotlin側で吸収する必要があります。
JSONレスポンスの例
{
"id": "1101",
"user_name": "Kitty White"
}
❌ 誤ったデータクラスの例
@Serializable
data class User(
val id: String,
val userName: String // キーの不一致が起こる
)
⭕ 正しいデータクラスの例
@Serializable
data class User(
val id: String,
@SerialName("user_name") // JSONのuser_nameをuserNameに割り当てる
val userName: String
)
キー名と異なるプロパティ名をつけたい場合には、上記のように @SerialNamecode>@SerialName</codeをつけることで適切に割り当てることができます。
💡 ポイント2:JSONが配列になっている時の対応
APIのレスポンスによっては、JSON内で配列(リスト形式)のデータが返ってくることがよくあります。
この場合、
Listとしてデータクラスを設計します。
JSONレスポンスの例
{
"users": [
{
"id": "1101",
"user_name": "Kitty White"
},
{
"id": "1102",
"user_name": "Mimmy White"
}
]
}
❌ 誤ったデータクラスの例
@Serializable
data class Response(
val users: User // 単一のオブジェクトになっている
)
@Serializable
data class User(
val id: String,
@SerialName("user_name")
val userName: String
)
⭕ 正しいデータクラスの例
@Serializable
data class Response(
val users: List<User> // List型で定義
)
@Serializable
data class User(
val id: String,
@SerialName("user_name")
val userName: String
)
usersキーから配列としてデータを取得するには、usersをList型として定義します。
上記のようにデータクラスを定義することで、配列のデータを適切にデコードすることができます。
💡 ポイント3:JSONの階層構造への対応
次に、JSONレスポンスが階層構造になっている場合の対応について紹介します。 よくある間違いとして、ネストされたJSONの構造に対して適切にデータクラスを設定できていないことが挙げられます。 実際に私もこの部分の見落としで、正しく動作せず苦労した経験がありました。
JSONレスポンスの例
{
"id": "1101",
"user_name": "Kitty White",
"urls": { // この部分がネストしている
"pc": "https://example.com",
"mobile": "https://m.example.com"
}
}
❌ 誤ったデータクラスの例
@Serializable
data class User(
val id: String,
@SerialName("user_name")
val userName: String,
val pc: String, // ネストされた構造に対応できていない
val mobile: String
)
⭕ 正しいデータクラスの例
@Serializable
data class User(
val id: String,
@SerialName("user_name")
val userName: String,
val urls: Urls // ネストしたJSON構造に対応する専用のクラスを追加
)
@Serializable
data class Urls(
val pc: String,
val mobile: String
)
💡 ポイント4:不要なキーを無視する設定 JSONレスポンスには、実際の処理には不要なキーが含まれていることがよくあります。 この場合、データクラスに存在しないキーが原因でデコード時にエラーが発生します。
JSONレスポンスの例
{
"id": "1101",
"user_name": "Kitty White",
"favorite_food": "apple pie",
}
データクラスの例
@Serializable
data class User(
val id: String,
@SerialName("user_name")
val userName: String
// favorite_food は不要なため定義していない
)
ここでは
favorite_foodプロパティが定義されていないため、以下のエラーが発生します。
エラーメッセージの例
Unexpected JSON token at offset 43: Encountered an unknown key 'favorite_food' at path: $.favorite_food
Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.
- offset 43:JSON文字列の先頭から数えて43番目の位置 (
favorite_foodの箇所) - at path: $.favorite_food:JSONのルートから見て
favorite_foodでエラーが発生している
この場合、以下のように設定することでエラーを回避できます。
⭕
ignoreUnknownKeysオプションを追加
val format = Json {
ignoreUnknownKeys = true // 不要なキーを無視する
}
val user = format.decodeFromString<User>("""
{
"id": "1101",
"user_name": "Kitty White",
"favorite_food": "apple pie"
}
""")
println(user)
Jsonオブジェクトの設定に
ignoreUnknownKeys = trueを指定することで、不要なキーを無視するようにします。
この設定を追加することで、データクラスに存在しないキーをスキップしてデコードできるようになります。
注意点
ignoreUnknownKeysは便利な設定ですが、必要なキーが漏れている場合に気がつけなかったり、デシリアライズに失敗していても無視されてしまうことがあります。
私もこれで、データクラスの間違い(ポイント3で紹介した階層の誤り)に気がつけず、アプリは動作しているのにデータが受け取れていないということがありました。
基本的には、レスポンスに含まれるキーは全てデータクラスに反映するようにし、無視しないように実装することが推奨されます。
まとめ
今回は、Serializationを使う際に注意したいポイントを紹介しました。
- JSONレスポンスとデータクラスは必ず一致させる。必要に応じて
@SerialNameを活用する。 - JSONの階層構造に適切に対応するため、ネストを意識してデータクラスを設計する。
- 不要なキーを無視したい場合は
ignoreUnknownKeysを活用するが、必要なデータを無視しないよう慎重に設定する。
我那覇あみ