Retrofit2.0に備えてKotlinで始めるMoshi(JSONパーサ）


Square製の軽量JSONパーサの[Moshi](https://github.com/square/moshi)をご存知でしょうか。今年の9月にv1.0がリリースされたところでまだ新しいライブラリですが、個人的に注目しております。

Androidの開発で以前まではGsonを使用していましたが、単純なJSONとPOJOの変換ならもっとシンプルで小さなライブラリがないかなと思っておりました。そこで出会ったのがMoshiです。

そして、現在開発が行われているRetofit2.0ではレスポンスとPOJOの変換の際に使用者がコンバータを選択できるようになり、Moshiも使用することもできます。
バージョンアップに合わせGsonからMoshiに移行しようと考えているため、基本的な使い方をおさえておきたと思い記事にしました。

合わせて、たろうさんがKotlinとJacksonで苦労したという記事を見て、Moshiは大丈夫か確認したかったので、Kotlinを使用しています。

* [Kotlinのdata classへJacksonでデシリアライズしようとして苦労した話(speakerdeck)](https://speakerdeck.com/ntaro/kotlinfalsedata-classhejacksondedesiriaraizusiyoutositeku-lao-sitahua-number-ashiyakt)

ライブラリやメソッド数を他のJSONパーサと比較してくださっている記事もありました。ライブラリサイズとメソッド数がGsonより抑えられており、Androidで使うには嬉しいですね。

* [RetrofitのJSONライブラリを使うときにしっておきたいこと](http://qiita.com/mattak@github/items/00878452bc2e8b71f493)

確認のために作成したサンプルはGitHubにあります。

* [GitHub](https://github.com/droibit/moshimoshi)

## Moshiの使い方

### 導入

プロジェクトに追加するにはGradleに依存関係を追加します。

```groovy:build.gradle
compile 'com.squareup.moshi:moshi:1.0.0'
```

### POJOの定義

以降で使用するクラスは次のようになります。
（スライドのよくあるクラスを参考にさせていただいております）

KotlinでPOJOを扱うならdata型にしたいので、プロパティはイミュータブルかつ、全てコンストラクタで設定するにしています。
（デバッグする際に#toString()をいい感じに用意してくれて便利でした）

フィールドにアノテーションをつけることもできますが、JSONのキーとプロパティ名が一致していれば、後はよしなにやってくれます。

```kotlin
// 記事を表すクラス
data class Article(
        val id: String, 
        val title: String, 
        val author: Author
)
// 記事の作者を表すクラス
data class Author(
        val id: String, 
        val name: String
)
```

JSONのキーとフィールド名を変わる場合などでは、`@Json` アノテーションを付けてやります。

```kotlin
// 記事を表すクラス 
data class NamedArticle(
        @Json(name = "articleId") val id: String,
                                  val title: String,
                                  val author: NamedAuthor
)

// 記事の作者を表すクラス
data class NamedAuthor(
        @Json(name = "authorId") val id: String,
                                 val name: String
)
```

他にプリミティブ型やコレクションをデフォルとサポートしています。
Androidのクラスをフィールドに使いたい場合は、後述のアダプタを書く必要があります。

### JSON <-> POJO変換

クラスとJSONの変換の流れは次のようになります。

```kotlin
// ① Moshiオブジェクトを生成
val moshi = Moshi.Builder().build()
// ② JSON<->POJO変換ためのアダプタ生成（変換対象のクラスを渡す）
val adapter = moshi.adapter(Article::class.java)

// ③JSONに変換するオブジェクトを生成
val article = Article(id = "10",
                      title = "MoshiMoshi",
                      author = Author(id = "1", name = "droibit"))
// ④ #toJson()でJSON文字列に変換                      
val json = adapter.toJson(article)
// ⑤ #fromJson()でJSON文字列からオブジェクトに変換
val restoreArticle = adapter.fromJson(json)
```

基本的な流れは、Moshiからクラス用のアダプタ作成して、JSONを放り込む用な感じです。上記の `Article` クラスから生成されたJSONは次のようになります。

```json
{
    "author": {
        "id": "1", 
        "name": "droibit"
    }, 
    "id": "10", 
    "title": "MoshiMoshi"
}
```

Articleはdata型でプロパティはイミュータブルなクラスですが、JSONから変換ができました。
（Moshiの内部ではリフレクションでフィールド値へセットしているようです）

### Custom Type Adapter

プリミティブ意外の型をPOJOに含めたい場合、カスタムアダプタを実装します。

```kotlin
data class Article(
        val id: String, 
        val title: String, 
        val author: Author,
	    val link: Uri  // ← New!!
)

class UriAdapter : JsonAdapter<Uri>() {

    companion object {
	    // Moshiのソースコードではアダプタ毎にファクトリを用意していたのでそれに習う。
	    // 多分、この方法が手っ取り早いと思われます。
	    // Uriなどのクラスごとに一つのアダプタを作成しなくてもOK。いくつかまとめてもOK
        val FACTORY: Factory = object: Factory {
            override fun create(type: Type, annotations: Set<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
                if (type === Uri::class.java) {
                    return UriAdapter()
                }
                return null
            }
        }
    }

    @Throws(IOException::class)
    override fun fromJson(reader: JsonReader): Uri {
        return Uri.parse(reader.nextString())
    }

    @Throws(IOException::class)
    override fun toJson(writer: JsonWriter, value: Uri) {
        writer.value(value.toString())
    }

    override fun toString(): String {
        return "JsonAdapter(Uri)"
    }
}

// Moshiオブジェクトを作成するときにファクトリを追加する。
val moshi = Moshi.Builder()
                 .add(UriAdapter.FACTORY)
                 .build()
// 後は同じ                 
```

ドキュメントにないので、ソースコードからプリミティブ型のアダプタを参考にしています。単純な変換ならこれでよさそうです。

詳しいところはこの辺りのクラスが参考になるかと思います。

* [StandardJsonAdapters.java](https://github.com/square/moshi/blob/master/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java)
* [ArrayJsonAdapter.java](https://github.com/square/moshi/blob/master/moshi/src/main/java/com/squareup/moshi/ArrayJsonAdapter.java)

また、JSONとPOJOが対になっていない場合、変換用のアダプタを追加することで上手く変換することができます。こちらは、Moshiの[ドキュメント](https://github.com/square/moshi#user-content-custom-type-adapters)がわかりやすいです。

### フィールドの除外

変換の際に特定にフィールドを除外したい場合は、`@Transient` アノテーションを使用します。
（Javaの場合は `transient` 修飾子）

```kotlin
class Article {
    @Transient var updated: String? = null
}
```
このようにアノテーションをつけるだけで自動的に除外してくれます。

* [Exclude fields from serialization and deserialization](https://github.com/square/moshi/issues/94)

### Retrofit 2.0-beta2と連携

最後に、Retrofit2.0-beta2のJSONコンバータにMoshiを使用する方法です。

####  導入

RetrofitでMoshiを使うためのコンバータは予め用意されているので、依存関係を追加するだけになります。

```groovy:build.gradle
compile "com.squareup.retrofit:retrofit:2.0.0-beta2"
compile "com.squareup.retrofit:converter-moshi:2.0.0-beta2"
```

#### 実装

ちょくちょくお世話になっている[livedoor お天気Webサービス](http://weather.livedoor.com/weather_hacks/webservice) では、天気情報をJSONで取得することができるので、それにRetrofitを使ってみます。

POJOを定義する際に、プロパティが多くなってくる場合は、コメントで `@property` と書いておくと後々わかりやすいのではないかと思います。

```kotlin
/**
 * @property location 予報を発表した地域
 * @property title タイトル
 * 以下略
 */
data class Weather(
        val location: Location,
        val title: String,
        val link: Uri,
        val publicTime: String,
        val description: Description,
        val forecasts: List<Forecast>,
        val pinpointLocation: PinpointLocation,
        val copyright: Copyright
)
// 以下略
```

地域ごとの番号をクエリに設定しGETリクエストすると天気情報が取得できます。

```kotlin
interface WeatherService {
    @GET("/forecast/webservice/json/v1")
    fun weather(@Query("city") city: String): Call<Weather>
}
```

あとはRetrofitの作法に則ってコールします。

```kotlin
val moshi = Moshi.Builder()
                 .add(UriAdapter.FACTORY)
                 .build()
val retrofit = Retrofit.Builder()
                       .baseUrl("http://weather.livedoor.com")
                       .addConverterFactory(MoshiConverterFactory.create(moshi))
                       .build()
val service = retrofit.create(WeatherService::class.java)

try {
	 val response = service.weather("130010").execute()
	 val weather = response?.body()
	 // 何かする
} catch (e: Exception) {
    // エラー処理
}
``` 

上記のような簡単な変換なら軽量なMoshiでも十分いけるのではと感じております。

### 注意点

JSONから上記のような `Article` クラスに変換する際、JSON側にキーが存在しないことがあると注意が必要です。
たとえば、`Article` の `title` がJSONに存在しない場合です。

```json
{
    "author": {
        "id": "1", 
        "name": "droibit"
    }, 
    "id": "10"
}
```

JSONからの変換は成功するのですが、生成された `Article` オブジェクトの `title` プロパティが **null** にっていることです。
コンストラクタの宣言では、オプショナル型にしていないにもかかわらず、値は **null** になります。（リフレクションを使っている関係かと思われます）

そのため、非オプショナル型の値として扱っているとヌルポが出る可能性があるのです！ Kotlinで！
そのため、String型の値などにはデフォルト値を設定しておくといいでしょう。

```kotlin
data class Article(
        val id: String = "", 
        val title: String = "", 
        val author: Author
)
```

以上、簡単ですがMoshiの使い方でした。

> Written with [StackEdit](https://stackedit.io/).