mike-neckのブログ

Java or Groovy or Swift or Golang

比較的新しいJava用のJSONライブラリーGenson

標記の件について、というわけでもないのですが、JSONPの実装ライブラリーを調べていた所、Gensonなるライブラリーがあるのを見つけました。

謳い文句としては

  • 簡単に使えてしかもパワフル
  • 他のJavaエコシステムとも協調性がある
    • JSR353(JSONP)実装
    • JAXBとも共に使える
    • JAX-RS(Jersey)でも使える(RESTEasyは試してないのでわからん)
    • Scala用のモジュールもある
    • Joda-Timeも使える
  • 小さくて速い
    • 速度ではJacksonにはかなわないけど、バイナリーサイズがJacksonよりも小さい
    • 速度ではGsonに余裕で勝てるし、バイナリーサイズがGsonと遜色ない

といったところです。

まあ、言い換えれば中途半端ということですが、作者のモティベーションを借りると、痒いところに手が届くJSONのライブラリーということらしいです。


使い方

maven/gradle - dependencies

mavenの人はこれを追加します。

<dependency>
  <groupId>com.owlike</groupId>
  <artifactId>genson</artifactId>
  <version>1.3</version>
</dependency>

gradleの人はこれを追加します。

dependencies {
    compile 'com.owlike:genson:1.3'
}

まずはJSONシリアライズ

こんなクラスを用意します。

public class RunRecord implements Serializable {
    private Integer metres;
    private Date date;
    // setter/getter省略
    @Override
    public String toString() {
        return new StringJoiner(", ", "RunRecord:[", "]")
                .add("metres: [" + (metres == null ? "null" : metres) + "]")
                .add("date: [" + (date == null ?
                        "null" :
                        SimpleDateFormat.getDateTimeInstance()
                                .format(date)) + "]")
                .toString();
    }
}

おもむろにこんなテストクラスを用意します。

なお、System.outがグチャグチャになるのが嫌なので、slf4jとlogback使っています。

public static class UseDateAsTimeStamp {
    private static final Logger LOGGER = LoggerFactory.getLogger(UseDateAsTimeStamp.class);
    private static final ZoneId ASIA_TOKYO = ZoneId.of("Asia/Tokyo");

    private Genson genson;
    private Date now;

    @Rule
    public TestName testName = new TestName();

    @Before
    public void setup() {
        genson = new GensonBuilder()
                .useDateAsTimestamp(true)
                .create();
        LocalDateTime current = LocalDateTime.now(ASIA_TOKYO);
        Instant instant = current.toInstant(ASIA_TOKYO.getRules().getOffset(current));
        now = Date.from(instant);
    }

    @Test
    public void serializeWithTimestamp() {
        RunRecord rec = new RunRecord(1000, now);
        String json = genson.serialize(rec);
        LOGGER.debug("{} -> {}", testName.getMethodName(), json);
    }
}

実行するとこんな感じです。

serializeWithTimestamp -> {"date":1437934805279,"metres":1000}

まあ、至って普通のJSONが吐き出されていますね。

上記のサンプルではGensonインスタンスを生成する際に、

genson = new GensonBuilder()
    .useDateAsTimestamp(true)
    .create();

と指定しており、Date型がlongの値で出力されるようになっています。

【2015/07/27 8:10 追記】なおこのuseDataAsTimestampはデフォルトではtrueに設定されてます。下のDateFormatを設定した場合でも、useDataAsTimestamptrueの場合にはuseDataAsTimestampが優先されます。【追記終わり】

「いやじゃ、儂はDateには特定のフォーマットがいいんじゃ」という人は、Gensonインスタンスを生成する際に次のように指定します。

genson = new GensonBuilder()
        .useDateAsTimestamp(false)
        .useDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"))
        .create();

このようにGensonインスタンスを生成した場合には、出力はこんな感じになります。

serializeWithISO8601 -> {"date":"2015-07-27T03:20:05.204+09:00","metres":1000}

まあ、これも至って普通のJSONです。

JSONからデシリアライズ

これもわりと簡単です。

public static class DeserializeWithTimeStamp {
    private static final Logger LOGGER = LoggerFactory.getLogger(DeserializeWithTimeStamp.class);
    private Genson genson;

    @Rule
    public TestName testName = new TestName();

    @Before
    public void setup() {
        genson = new GensonBuilder()
                .useDateAsTimestamp(true)
                .create();
    }

    @Test
    public void deserializeWithTimestamp() {
        String json = "{\"date\":1437934296467,\"metres\":200}";
        RunRecord rec = genson.deserialize(json, RunRecord.class);
        LOGGER.debug("{} -> {}", testName.getMethodName(), rec);
    }
}

これを実行すると、次のように出力されます。

deserializeWithTimestamp -> RunRecord:[metres: [200], date: [2015/07/27 3:11:36]]

これも至って普通にPOJOになりました。

「いやじゃ、儂は特定のフォーマットがいいんじゃ」という人は(ry。

JSON Arrayのデシリアライズ

JSON Arrayのデシリアライズも型を指定して行うことができます。

こんな感じです。

@Test
public void deserializeArray() {
    String json = "["
        + "{\"date\":\"2015-07-26T23:57:54.124+09:00\",\"metres\":1000}"
        + ","
        + "{\"date\":\"2015-07-27T00:08:11.748+09:00\",\"metres\":1000}"
        + "]";
    List<RunRecord> recs = genson.deserialize(json, new GenericType<List<RunRecord>>() {});
    LOGGER.debug("{} -> {}", testName.getMethodName(), recs);
}

実行結果はこんな感じ。

deserializeArray -> [
    RunRecord:[metres: [1000], date: [2015/07/26 23:57:54]],
    RunRecord:[metres: [1000], date: [2015/07/27 0:08:11]]]

なお、実行結果は読みやすくするために勝手に改行入れてます。

GenericTypeというクラスのジェネリクスに変換したい型を指定すれば、あとはよしなにやってくれます。なお、GenericTypeクラスのインスタンス化の後に{}があるのはGenericTypeクラスが抽象クラスだからなのですが、必要そうなメソッドはすべて実装されているので、特に何かメソッドを実装する必要はありません(魔改造したいなら別だけど、特に改造するところが見当たらない)。


え?Jacksonの方が簡単?

あー、そうですね、Jacksonの方が簡単ですね(棒)

作者曰く、

他のライブラリーって機能が足りないとか、機能が豊富すぎて、自分好みにカスタマイズするの面倒じゃけぇ、このライブラリーでその辺を解決したんよ

READMEより意訳


Android

Androidのことは特に書いてなかったですね。

配布されたjarのMANIFESTを読んだところ、コンパイルしたJDKのバージョンが1.7.0_65でした。また、pomの依存を見たところ、jersey-commonとかjavax.servlet-apiとかasm-commonsとかspring-coreとかresteasy-jaxrsとかありましたけど、全部optional要素がtrueになっていたので、特に依存ライブラリーは必要ないようです。だから、多分Androidでも使えるんじゃないかな。まあ、Androiderの人はGsonかJsonPullParser使うだろうから、Gensonを使うことはないだろうけど…

Scala

あっ(試すの忘れた…

Jersey

Jerseyでも試そうと思って、今流行りのpayara-microでやることにしましたが、こちらはだいぶハマったので別の機会に…

おわり