TL;DR
Moshiのkotlin extensionであるKotlinJsonAdapter
を使うと、
kotlinの機能であるnon-nullや、default値を考慮したJSONパースを行ってくれます。
GsonとKotlinの相性が良くない
私は普段、JSONパーサーにGsonを使っています。
Gsonに強いこだわりはありませんが、Androidでは広く使われており、ネット上に情報が豊富にあるのが使用の主な理由です。
しかし、JSONのパースした結果をKotlinのclassに入れる時に、Gsonだと都合が悪いことがわかりました。
よくない理由
Kotlinでは、property
を宣言する時に特に何も指定しなければnon-nullになります。
non-nullなproperty
にnull
を渡した場合は例外がthrowされ、classが不正な状態になることを自動で防いでくれます。
data class Dto(
val foo: String, // non-null
val bar: String // non-null
)
従って、Gsonを使って、以下のようなbar
というkeyがないJSONをパースした時に、
例外がthrowされるだろうと思っていました。
{"foo": "value"}
しかし、実際は例外はthrowせず、foo
にnull
という値が入ってしまいます。
結果、classで定義したnon-nullという不変条件が破られてしまいます。
val dto = Gson().fromJson(json, Dto::class.java) //この時点では例外はthrowされない
dto.bar.length // barにはnullが入っており、この時点でNPEがthrowされる
この問題を解決するのにMoshiというライブラリを使います。
Moshiとは
MoshiはSquareが開発したJSONパーサーです。
https://github.com/square/moshi
Square自身もGsonを使っていたそうですが、いくつか不満があり、Moshiの開発に至ったそうです。参考
そのような経緯もあり、Gsonよりも使いやすい部分が多いという印象です。
Moshiのkotlin extensionを使い、Kotlinとの相性を良くする
このMoshiに、Kotlinのclassへのパースをサポートする機能が、今年5月に追加されました。
https://github.com/square/moshi/blob/master/CHANGELOG.md#version-150
Kotlin models are now supported via the moshi-kotlin extension.
KotlinJsonAdapterFactory is the best way to use Kotlin with Moshi.
It honors default values and is null-safe. Kotlin users that don't use this factory should write custom adapters for their JSON types. Otherwise Moshi cannot properly initialize delegated properties of the objects it decodes.
このextensionの機能
このextension
を使うことで、以下のようなKotlin特有の言語機能をMoshiが補完してくれます
- non-nullの
property
に相当するJSONの値がなかった場合、例外をthrowしてくれる - nullableのpropertyは当然例外はthrowせず、
null
が入る
-
property
に相当するJSONの値がなかったが、そのproperty
にdefault値を指定した場合は、その値が入る- ただし、JSONの値が明示的に
null
となっていた場合は、propertyにnull
が入る
- ただし、JSONの値が明示的に
より詳細の機能を知りたい場合は、このextensionのテストコードを見ると良いと思います。
このコードから、特定のJSONとkotlinのclassがどう変換され合うのかが分かります。
https://github.com/square/moshi/blob/master/kotlin/src/test/java/com/squareup/moshi/KotlinJsonAdapterTest.kt
導入方法
導入方法はextensionをdependencyに追加し、Moshiのインスタンスを作る時に、extensionの本体であるKotlinJsonAdapterFactory
をaddします。
// dependenciesに以下を追加
def moshiVerion = '1.5.0'
compile "com.squareup.moshi:moshi:$moshiVerion"
compile "com.squareup.moshi:moshi-kotlin:$moshiVerion"
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory()) // kotlin extensionを指定
.build()
val dto = moshi.adapter(Dto::class.java).fromJson("json")
導入結果
Gsonを使うことで問題となっていた「non-nullなpropertyにnullが入ってしまうという」という点が、
これを導入すると、「インスタンスにnullが入ることなく、パース時に例外がthrowされる」ようになります。
data class Dto(
val foo: String, // non-null
val bar: String // non-null
)
// jsonにbarが存在しない
val json = """
{"foo": "value"}
"""
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
// barが存在しないため、パース時にexceptionが発生
val dto = moshi.adapter(Dto::class.java).fromJson(json)
まとめ
Moshiのkotlin extensionを使うことで、Kotlinのnull-safetyのような言語機能を保ったままJSONをパースしてくれます。
今Kotlinを使っていたり、今後Kotlinを追加しようとしている場合は、
Moshiの導入を検討されてはいかがでしょうか。