Androidの高速なJSON パーサ/ジェネレータ、LoganSquareを使う


Androidのアプリでそこそこ巨大なJSONをパースする必要があったのですが、jackson-databind や GSONではパースに時間がかかりすぎて辛い。。。
またJacksonのStreaming APIはパフォーマンスはめちゃくちゃいいのですが、パース処理を自分で書いていくのは大変。。。

そんな時に見つけたのがLoganSquareです。
https://github.com/bluelinelabs/LoganSquare

LoganSquareはモデルクラスにアノテーションを付け、それによりJacksonのStreaming APIを使用したパース処理が生成されます。
つまりお手軽にJackson Streaming APIの恩恵を受けることが出来ます!

こちらがベンチマークの結果。速い。

LoganSquareの使い方

セットアップ

build.gradleにLoganSquareを追加します。

build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
}
apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    apt 'com.bluelinelabs:logansquare-compiler:1.1.0'
    compile 'com.bluelinelabs:logansquare:1.1.0'
}

Proguard

Proguardの設定はこのように

proguard-rules.pro
-keep class com.bluelinelabs.logansquare.** { *; }
-keep @com.bluelinelabs.logansquare.annotation.JsonObject class *
-keep class **$$JsonObjectMapper { *; }

Modelの作成

LoganSquareではモデルクラスに @JsonObject アノテーションをつけることにより、そのクラスのフィールドとJSONをマッピングします。

LoganSquareのパースさせるモデルクラスを作成するには次の通り3つのやり方があります。

1. すべてのフィールドにアノテーションを付けるやり方

デフォルトな方法はこちら。
パースするフィールドに JsonField アノテーションを付けます。


@JsonObject
public class User {

    @JsonField
    public long id;

    @JsonField
    String name;

    @JsonField
    private String email;

    private String company;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }
}

この例ではpublicな id とパッケージローカルフィールドの name 、Getter/Setterのある email がパースされます。
@JSONField がついていない companay はパースされません。

この方法だとパースする全てのフィールドに @JSONField アノテーションを付けなければいけません。
フィールド数が多い場合は、次の2や3の方法が良いかと思います。

2. private以外のフィールドをパースするやり方

private以外の定義されているフィールドをパースする方法です。
publicまたはパッケージローカルの全てのフィールドがパースされます。

@JsonObjectfieldDetectionPolicy = JsonObject.FieldDetectionPolicy.NONPRIVATE_FIELDS と指定してやります。


@JsonObject(fieldDetectionPolicy = JsonObject.FieldDetectionPolicy.NONPRIVATE_FIELDS)
public class User {

    public long id;

    public String name;

    String email;

    @JsonIgnore
    public String company;

}

@JsonIgnore でprivate以外のフィールドをパースから除外することも出来ます。

3. private以外、またはアクセサーがあるフィールドをパースするやり方

2のやり方 + アクセサーを持つフィールドがパースされます。
@JsonObject
fieldDetectionPolicy = JsonObject.FieldDetectionPolicy.NONPRIVATE_FIELDS_AND_ACCESSORS を指定します。


@JsonObject(fieldDetectionPolicy = JsonObject.FieldDetectionPolicy.NONPRIVATE_FIELDS_AND_ACCESSORS)
public class User {

    public long id;

    public String name;

    private String email;

    @JsonIgnore
    public String company;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

JSONのパース

InputStreamを渡す方法とStringを渡す方法があります。

InputStreamからパース


InputStream is = ...;
User user = LoganSquare.parse(is, User.class);

Stringからパース


String jsonStr = "{\"id\": 12345,\"name\": \"kazutoyo\",\"email\": \"[email protected]\",\"age\": 25,\"company\": \"(๑´ڡ`๑)ぺろり\"}";
User User = LoganSquare.parse(jsonStr, User.class); 

JSONの生成

OutputStreamに流す方法とStringにする方法があります。

OutputStreamへ


LoganSquare.serialize(user, os);

Stringへ


String jsonStr = LoganSquare.serialize(user);

その他

フィールド名の指定

モデルのフィールド名とJSONのフィールド名が違う場合、 @JsonFieldname オプションを指定することができます。

{"_id": 12345}

といったJSONのフィールド名 _id で、モデルでのフィールド名が id と定義していた場合


@JsonField(name = "_id")
public long id;

とすることでマッピングできます。

TypeConverter

LoganSquareにはTypeConverterというパースする際にデータを整形してくれる機能があります。
例えば、intの値を日本円の表示に変換するTypeConverterは次のような感じです。

CurrencyFormatConverter.java
public class CurrencyFormatConverter extends IntBasedTypeConverter<String> {

    @Override
    public String getFromInt(int i) {
        return NumberFormat.getCurrencyInstance(Locale.JAPAN).format(Integer.valueOf(i));
    }

    @Override
    public int convertToInt(String object) {
        try {
            NumberFormat.getCurrencyInstance(Locale.JAPAN).parse(object).intValue();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return 0;
    }
}
Item.java
@JsonObject(fieldDetectionPolicy = JsonObject.FieldDetectionPolicy.NONPRIVATE_FIELDS_AND_ACCESSORS)
public class Item {

    int id;

    String name;

    @JsonField(typeConverter = CurrencyFormatConverter.class)
    String price;
}

まとめ

このように、LoganSquareは手軽にJackson Streaming APIを扱うことが出来ます。
JSON処理のパフォーマンスにお悩みの方はぜひ使ってみてください!