jsonpullparser

2013-11-14T00:00:00+00:00 Java

jsonpullparserを使ってみた

検証環境の構築

jsonpullparser-coreとjsonpullparser-aptが最低限必要。但しapt側は実際動かす場合には必要なくあくまでアノテーションプロセッサーによるソースコードの生成を利用する

apply plugin: "java"
apply plugin: "eclipse"

repositories {
    mavenCentral()
}

dependencies {
    compile "net.vvakame:jsonpullparser-core:1.6.2"
    compile "net.vvakame:jsonpullparser-apt:1.6.2"

    testCompile "junit:junit:4.11"
    testCompile "org.hamcrest:hamcrest-all:1.3"
}

compileJava {
    options.compilerArgs = ["-s", "apt_generated"]
}

な感じでcompileタスクプロセス上でjsonpullparser-aptが処理されれば良いんじゃないかと。あとEclipseとかでやる場合には注釈処理・ファクトリーパス等の設定しておくだけで良い

で利用するJSONデータ自体は https://api.github.com/users/kinjouj/repos なGithub APIを利用する。これが手っ取り早いので

Repository.java

package sample.model;

import net.vvakame.util.jsonpullparser.annotation.JsonKey;
import net.vvakame.util.jsonpullparser.annotation.JsonModel;

@JsonModel
public class Repository {

    public static final String URL = "https://api.github.com/users/kinjouj/repos";

    @JsonKey
    int id;

    @JsonKey
    String name;

    @JsonKey
    User owner;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User getOwner() {
        return owner;
    }

    public void setOwner(User owner) {
        this.owner = owner;
    }
}

基本は@JsonModelを利用する。でプロパティに@JsonKeyなアノテーションを利用してマッピングを行う。でオプション参考に書いてるけど、@JsonModelなクラスであれば型変換を行なってくれる

ちなみに

  • セッターメソッドが無いと「can't find setter method」
  • ゲッターメソッドが無いと「can't find getter method」

っていうように怒られる

User.java

package sample.model;

import java.net.URL;

import net.vvakame.util.jsonpullparser.annotation.JsonKey;
import net.vvakame.util.jsonpullparser.annotation.JsonModel;

@JsonModel(
    /*
    true の場合 foo_bar というJSONに対して fooBar という変数を対応させる。
    @JsonKeyでも指定出来るが優先度はJsonKeyで指定されている方を優先するらしい
    */
    decamelize = true

    /*
    true の場合 未知のKeyを持つJSONを読み込ませた場合、例外を発生させる。
    treatUnknownKeyAsError = true
    */
)
public class User {

    @JsonKey
    String name;

    // java.net.URL型は対応していないのでTokenCoverterを利用して型変換
    @JsonKey(converter = URLTokenConverter.class, out = false)
    URL htmlUrl;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setHtmlUrl(URL htmlUrl) {
        this.htmlUrl = htmlUrl;
    }

    public URL getHtmlUrl() {
        return htmlUrl;
    }
}

書いてあるとおりjava.net.URLは型変換がデフォルトでサポートされていないのでTokenConverterを利用してサポートされていない型への変換を行わないとマッピング出来ない

でまぁ@JsonModelなクラスを定義するとアノテーションプロセッサーによって、デフォルトではGenなサフィックスが付与されたJavaクラスソースが生成される

URLTokenConverter.java

package sample.model;

import java.io.IOException;
import java.io.Writer;
import java.net.URL;

import net.vvakame.util.jsonpullparser.JsonFormatException;
import net.vvakame.util.jsonpullparser.JsonPullParser;
import net.vvakame.util.jsonpullparser.JsonPullParser.State;
import net.vvakame.util.jsonpullparser.util.OnJsonObjectAddListener;
import net.vvakame.util.jsonpullparser.util.TokenConverter;

public class URLTokenConverter extends TokenConverter<url> {

    static URLTokenConverter converter = null;

    public static URLTokenConverter getInstance() {
        if (converter == null) converter = new URLTokenConverter();

        return converter;
    }

    @Override
    public URL parse(JsonPullParser parser, OnJsonObjectAddListener listener) throws IOException,
    JsonFormatException {
        URL url = null;
        if (parser.getEventType() == State.VALUE_STRING)
            url = new URL(parser.getValueString());

        return url;
    }

    // out=trueで且つencodeする場合にはこれをオーバーライドする
    @Override
    public void encodeNullToNull(Writer writer, URL obj) throws IOException {
        writer.write(obj.toString());
    }
}

RepositoryTest.java

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.HttpsURLConnection;

import org.junit.Test;

import sample.model.Repository;
import sample.model.RepositoryGen;
import sample.model.User;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

public class RepositoryTest {

    @Test
    public void test_parse() throws Exception {
        List<repository> repositories = new ArrayList<repository>();
        URL url = new URL(Repository.URL);
        HttpsURLConnection conn = null;

        try {
            conn = (HttpsURLConnection)url.openConnection();
            repositories = RepositoryGen.getList(conn.getInputStream());
        } finally {
            if (conn != null) {
                conn.disconnect();
                conn = null;
            }
        }

        assertThat(repositories, hasSize(30));

        Repository repository = repositories.get(0);
        assertThat(repository, notNullValue());
        assertThat(repository.getName(), is("AATwitter"));

        User owner = repository.getOwner();
        assertThat(owner, notNullValue());
        assertThat(owner.getHtmlUrl(), instanceOf(URL.class));
        assertThat(owner.getName(), is("...")); // fail
    }
}

コメントで書いてるように最後でテストがfailする。理由としてUser.javaが参照するのは https://api.github.com/users/kinjouj で参照できるのと https://api.github.com/users/kinjouj/repos で参照できるownerに互換性が無い。今回はパースする対象が前者のURLの方なので、ownerなJSONにnameが無いので

でアノテーションプロセッサーによって生成されたクラスのgetメソッドのオーバーライドメソッドに色々あるので引数がStringとかのメソッドもあるのでそういうのを利用する事でJSONリクエストをパースしたりとかも出来る

余談: ハッシュ(オブジェクト)形式をTokenConverterでパースする

例えば上記のUserクラスが@JsonModelでは無いケースを利用する場合にはデフォルトで型変換な方式が行われないので、@JsonKeyで参照する事自体がコンパイルすら出来ない。そういう場合は上記でも書いたようにTokenConverterを作れば良い

package sample.model;

import java.io.IOException;
import java.net.URL;

import net.vvakame.util.jsonpullparser.JsonFormatException;
import net.vvakame.util.jsonpullparser.JsonPullParser;
import net.vvakame.util.jsonpullparser.JsonPullParser.State;
import net.vvakame.util.jsonpullparser.util.OnJsonObjectAddListener;
import net.vvakame.util.jsonpullparser.util.TokenConverter;

public class UserTypeConverter extends TokenConverter<user> {

    static UserTypeConverter converter;

    public static UserTypeConverter getInstance() {
        if (converter == null) {
            converter = new UserTypeConverter();
        }

        return converter;
    }

    @Override
    public User parse(JsonPullParser parser, OnJsonObjectAddListener listener)
        throws IOException, JsonFormatException {

        User user = null;

        State state = parser.getEventType();
        if (state == State.START_HASH) {
            user = new User();
            parse(parser, user);
        }

        return user;
    }

    void parse(JsonPullParser parser, User user) throws IOException, JsonFormatException {
        String key = null;
        State state;
        while ((state = parser.getEventType()) != State.END_HASH) {
            switch (state) {
                case KEY:
                    key = parser.getValueString();
                    break;

                case VALUE_STRING:
                    String value = parser.getValueString();

                    switch (key) {
                        case "login":
                            user.setName(value);
                        break;

                        case "html_url":
                            user.setHtmlUrl(new URL(value));
                    }

                    break;

                default:
                    key = null;
            }
        }
    }
}

Annotation Processorの基本的な所