Android
Kotlin

Kotlinと相性が良いMoshiのkotlin extensionを使う

More than 1 year has passed since last update.

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なpropertynullを渡した場合は例外がthrowされ、classが不正な状態になることを自動で防いでくれます。

JSONを表したDtoクラス
data class Dto(
  val foo: String, // non-null
  val bar: String // non-null
)

従って、Gsonを使って、以下のようなbarというkeyがないJSONをパースした時に、
例外がthrowされるだろうと思っていました。

barが存在しない
{"foo": "value"}

しかし、実際は例外はthrowせず、foonullという値が入ってしまいます。
結果、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が入る

より詳細の機能を知りたい場合は、この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します。

app/build.gradle
// 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の導入を検討されてはいかがでしょうか。