Puree v3またはorg.json.JSONObjectのかわりにGsonのJsonObjectを使う話

三行まとめ

  • puree-androidを全面的に com.google.gson.JsonObject を使うように書き換えてv3にした
  • JsonObjectはJSONObjectとほぼ同様に使うことが出来るがチェック例外がないのでほとんどの用途で使いやすい
  • JsonObjectの構築はJSONObjectより少し高速で文字列化の速度はほぼ同じ

概要

Pureeを v3 をリリースしました。

github.com

Pureeについては モバイルアプリのログ収集ライブラリ「Puree」をリリースしました - クックパッド開発者ブログ にて。製品に導入して半年たちましたが、いくつかバグ修正しつつ大きな問題もなく運用しています。

ところでこのほどPureeを大きく書き換えてv3としました。これは、 org.json.JSONObject を使うのをやめて、 com.google.gson.JsonObject を全面的に採用したのがもっとも大きい変更です。また、 Puree のstatic methodはほぼすべて PureeLoggerインスタンスメソッドを呼び出すだけになりました。これによりPureeLog関連のテストを書いたり、テスト時にPureeを無視したりするのが非常に簡単になりました。

Gson vs JSONObject

GsonJavaオブジェクトをそのままJSONシリアライズしたりデシリアライズしたりする機能をもつJSONライブラリです。Gsonの提供するクラスに org.json.JSONObject とほぼ同じ機能をもつ com.google.gson.JsonObject というクラスがあり、これを使うことでJSONObjectを使わずに済むというわけです。

ご存知の通り、JSONObjectは過剰にチェック例外(JSONException)を使っていて、確実に例外を投げない場面でもtry-catchしなければいけません。下手に JSONObject#put(String key, Object value) というメソッドがあるために型チェックが働かないというのもインターフェイスの設計ミスといえます。現実的には、プログラム中で静的にJSONObjectを構築する限りJSONExceptionが投げられることはほんとんどないにも関わらずです。そして、ほとんど投げられることのないチェック例外のあるコードを多用していると、アプリケーション自体のコードが徐々に荒れてきます。

GsonのJsonObjectの構築するメソッド群には、Objectを入れるメソッドがありません。そして、チェック例外もありません。煩雑なtry-catchはなく、扱いは簡単で、コードの秩序も保たれます。 JSONMap<K, V> のように構築したいのであれば、GsonのJsonObjectを使うほうがいいでしょう。

ただし、GsonはJSONObjectよりも寛容すぎる点もあります。たとえば、NaNとInfinityはJSON標準では認められない値ですが、Gsonはシリアライズしてしまいます。つまり、以下のようになります。

public class GsonTest {
    @Test
    public void testJsonObjectSerialization() throws Exception {
        JsonObject json = new JsonObject();
        json.addProperty("nan", Double.NaN);
        json.addProperty("pinf", Double.POSITIVE_INFINITY);
        json.addProperty("ninf", Double.POSITIVE_INFINITY);

        assertThat(json.toString(), is("{\"nan\":NaN,\"pinf\":Infinity,\"ninf\":Infinity}"));
    }
}

一方、JSONObjectはJSON標準通りこれを認めず、例外を投げます。

public class JSONObjectTest {
    @Test(expected = JSONException.class)
    public void testJSONObjectSerializationOfNaN() throws Exception {
        JSONObject json = new JSONObject();
        json.put("nan", Double.NaN);
    }

    @Test(expected = JSONException.class)
    public void testJSONObjectSerializationOfPositiveInfinity() throws Exception {
        JSONObject json = new JSONObject();
        json.put("pinf", Double.POSITIVE_INFINITY);
    }

    @Test(expected = JSONException.class)
    public void testJSONObjectSerializationOfNegativeInfinity() throws Exception {
        JSONObject json = new JSONObject();
        json.put("ninf", Double.NEGATIVE_INFINITY);
    }
}

実行可能なテストコード

この点は注意が必要です。

パフォーマンス

ざっと測った感じだと、シリアライズはGsonのほうがやや高速で、デシリアライズはほぼ同じ水準でした。高速にならないのは残念ですが、GsonのJsonObjectに切り替えてもパフォーマンスが劣化するということはなさそうです。最近はもっと高速なJSONプロセッサもあるのですが、JSONObjectの代替ということであればGsonでよいのではないかと思っています。

実行可能なベンチマークコード