Moshi を使って Java オブジェクトと JSON を相互変換する

  • 1
    いいね
  • 0
    コメント

概要

Java のオブジェクトを JSON に変換してファイルに保存したり、保存した JSON を Java のオブジェクトに変換するという処理を実装するのに、 Jackson を使うまでもないが Android SDK 標準の org.json を使うのは少々つらいという状況で、何か良いライブラリはないかと探したところ、 Square, Inc.Moshi というライブラリがあると知ったので、簡単に試してみました。


Moshi

Moshi は Square Inc. が開発した JSON ライブラリです。Droidcon Montreal 2015 で Ok シリーズの1つとして紹介されていたようです。

A Few 'Ok' Libraries (Droidcon MTL 2015)

今回は触れませんが、Ok シリーズではほかに HTTP クライアントの OkHttp や、I/O ライブラリの Okio 等がリリースされています。

バージョン

この記事を書いている 2017/08/12 現在では、最新のリリースは 1.5.0 (2017/05/15)です。

ファイルサイズ

ファイルサイズが 64KB と小さいです。これが Android アプリ開発ではメリットになりえます。
Jackson が 1MB 近く、Gson も最新の 2.8.1 が 144KB であることを考えると、結構小さいです。

1.4.0 の API ドキュメント によると、本体に含まれるクラスは下記の通りです。

  1. FromJson
  2. Json
  3. JsonAdapter
  4. JsonAdapter.Factory
  5. JsonDataException
  6. JsonEncodingException
  7. JsonQualifier
  8. JsonReader
  9. JsonReader.Options
  10. JsonReader.Token
  11. JsonWriter
  12. Moshi
  13. Moshi.Builder
  14. ToJson
  15. Types

依存

GitHub リポジトリのソースコードを軽く見た程度だと、test の方で android.util.Pair を使っている程度で、ライブラリの実装内では Android SDK の依存が含まれていないようです。そのため、他の Ok シリーズ同様に Android アプリ以外でも使うことが可能です。

なお、Okio が依存に含まれています。こちらも Android SDK の依存は持たない I/O ライブラリです。

ライセンス

1.5.0 では他の Ok シリーズ同様に Apache 2.0 でした。


導入

依存の追記

1行追加するだけで使えます。

app/build.gradle
dependencies {
  compile 'com.squareup.moshi:moshi:1.5.0'

Android アプリの場合は Proguard の設定

GitHub リポジトリの README によると、 Proguard を使う場合は下記の設定が必要とのことです。Java アプリケーションで使う分にはこの設定は不要です。

-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
-keepclasseswithmembers class * {
    @com.squareup.moshi.* <methods>;
}
-keep @com.squareup.moshi.JsonQualifier interface *

前提

実行環境

項目
IDE Android Studio 2.3 & IntelliJ IDEA
Java 1.8.0_131
OS Windows 10

以下、今回の動作検証で必要なサンプルクラスの説明が続きますので、不要であればここをクリックして飛ばしてください

今回使うクラス

Web ブラウザのタブを定義するクラスを扱ってみます。

Tab.java
import java.util.ArrayList;
import java.util.List;

class Tab {

    private List<History> histories;

    private int index;

    private String thumbnailPath;

    private String lastTitle;

    Tab() {
        histories = new ArrayList<>();
        index = -1;
    }

    // Simple accessor methods...

    void addHistory(final History history) {
        histories.add(history);
        index++;
    }

    History getLatest() {
        return histories.get(index);
    }
}

Tab クラス内で使っている History クラスは下記の通り、 String 型のフィールドを2つ持つだけの簡単なクラスです。

History.java
class History {

    private final String title;

    private final String url;

    private History(final String title, final String url) {
        this.title = title;
        this.url   = url;
    }

    public static History make(final String title, final String url) {
        return new History(title, url);
    }

    public String title() {
        return title;
    }

    public String url() {
        return url;
    }

}

準備

以降の手順に先立ち、Tab オブジェクトを初期化しておきます。

final Tab tab = new Tab();
tab.setThumbnailPath("file://~~");
tab.setLastTitle("Google");
tab.addHistory(History.make("Title", "URL"));

Java オブジェクトと JSON を相互変換する

Moshi では JsonAdapter というクラスで変換を実施します。

JsonAdapter の取得

Moshi オブジェクトを生成し、そこからadapter メソッドで変換対象の型を指定して JsonAdapter のオブジェクトを取得します。

final Moshi moshi = new Moshi.Builder().build();
final JsonAdapter<Tab> tabJsonAdapter = moshi.adapter(Tab.class);

Java オブジェクト -> JSON の変換

JsonAdapter.toJson で実行します。

final String json = tabJsonAdapter.toJson(tab);

出力

下記の JSON が出力されます。

{"histories":[{"title":"Title","url":"URL"}],"index":0,"lastTitle":"Google","thumbnailPath":"file://~~"}

Pretty Printing

JSON を整形した状態で出力したい場合は少し手間がかかります。

pretty_print
final Buffer buffer = new Buffer(); // 1.
final JsonWriter prettyPrintWriter = JsonWriter.of(buffer); // 2.
prettyPrintWriter.setIndent("  "); // 3.
try {
    tabJsonAdapter.toJson(prettyPrintWriter, tab); // 4.
} catch (final IOException e) {
    e.printStackTrace();
}

final String json = buffer.readUtf8(); // 5

手順

  1. Okio の Buffer オブジェクトを用意
  2. 1 の Buffer オブジェクトを対象とする JsonWriter オブジェクトを取得
  3. 2 の JsonWriter に任意のインデントを設定
  4. JsonAdapter.toJson に JsonWriter と Java オブジェクトを渡して実行
  5. 1 の Buffer オブジェクトに書き込みが実行されているので、read して文字列を取り出す

使うクラスが増えたのと、 toJson が直接値を返さなくなったのと、toJson が例外を出すようになったので、少々戸惑うかもしれません。

出力結果

下記の通り、指定したインデントで JSON が出力されました。

{
  "histories": [
    {
      "title": "Title",
      "url": "URL"
    }
  ],
  "index": 0,
  "lastTitle": "Google",
  "thumbnailPath": "file://~~"
}

備考

なお、実用性は皆無ですが、前述の手順 3. でインデントとして渡す文字列は何でも構わないようで、例えば * 2つをインデントに指定した場合は下記の JSON が出力されます。

{
**"histories": [
****{
******"title": "Title",
******"url": "URL"
****}
**],
**"index": 0,
**"lastTitle": "Google",
**"thumbnailPath": "file://~~"
}

もちろん、この JSON を後述の JsonAdapter.fromJson に渡すと JSON としてパースできないので例外が発生します。

JSON -> Java オブジェクトの変換

JsonAdapter.fromJson で実行します。このメソッドでは IOException が発生しうるので、そのケアが必要です。

try {
    final Tab fromJson = tabJsonAdapter.fromJson(json);
} catch (IOException e) {
    e.printStackTrace();
}

出力

得られた Tab オブジェクトの中身を標準出力で表示してみます。

System.out.println(fromJson.getLastTitle());
System.out.println(fromJson.getThumbnailPath());
System.out.println(fromJson.getLatest().title());
System.out.println(fromJson.getLatest().url());
Google
file://~~
Title
URL

独自に定義した History クラスもちゃんと変換してくれるようです。public なコンストラクタを持たないクラスであってもちゃんと変換できるのが良いと感じます。

ソースコード

今回の動作検証用コードは gist に置いておきます。
https://gist.github.com/toastkidjp/252bc2ac86de2c0a3625b9fed0b9fc62


まとめ

Moshi は軽量で扱いやすい JSON ライブラリです。ライブラリ本体に Android SDK の依存を含まないため、 Android に限らず通常の Java アプリケーション開発でも利用できます。この Moshi を使った簡単な相互変換の方法について述べました。

参考