2013年2月7日木曜日

JSONICでis-a関係を扱う

JSONとPOJO(Javaオブジェクト)を相互変換するのに JSONIC という便利なライブラリがある。 実際の使い方はリンク先を見ていただくとして、単純な変換であればJSON→POJO、POJO→JSONとも1行のメソッド呼び出しで書けるのでとても楽。 ただ、is-a関係のあるオブジェクトを含むJSONをPOJOに変換する場合にどうしたらよいのか今までやり方がわからなくて困っていた。 たとえば、以下のような3つのクラスがあって、AとBがis-a関係、SとAが has-a 関係にあるとする。
public class A {
    private String fieldA;
    public String  getFieldA() { return fieldA; }
    public void setFieldA(String fieldA) { this.fieldA = fieldA; }
}

public class B extend A {
    private String fieldB;
    public String  getFieldB() { return fieldB; }
    public void setFieldB(String fieldB) { this.fieldB = fieldB; }
}

public class S {
    private A obj;
    public A getObj() { return obj; }
    public void setObj(A obj) { this.obj = obj; }
}
このときSのsetObj()にクラスBを持たせたPOJOをJSONに変換すると以下のようになる。
{
    "Obj" : { "fieldA":"~", "fieldB":"~" }
}
しかしJSONになると型情報がなくなるため、このJSONを以下のようにPOJOに逆変換すると S.getObj()で得られるインスタンスはクラスAになってしまう。
S myobj = JSON.decode(jsonText, S.class);

// o はクラスAのインスタンス
A o = myObj.getObj();
このときクラスBを再生するにはJSONICの変換ロジックをオーバーライドしてカスタマイズすることでできるらしい。 このように変換方法をカスタマイズするにはJSON.decode()メソッドではなく、postparse()をオーバーライドしたJSONクラスのインスタンスを作りparse()メソッドで変換をするようにするらしい。 少しややこしいが以下の方法でクラスBが再生できるようになった。
JSON json = new JSON() {
  // parse時の変換方法をカスタマイズする
  @Override
  protected <T> T postparse(Context context, Object value, Class<? extends T> c, Type type) throws Exception {
    // クラスAの変換であればクラスBとみなして変換をする
    if (c.equals(Class.A)) {
      return c.cast(super.postparse(context, value, Class.B, Class.B));
    }
    // クラスA以外ならば既定の方法で変換
    else {
      return super.postparse(context, value, c, type);
    }
  }
};
S myobj = json.parse(jsonText, S.class);

// o はクラスBのインスタンス
A o = myObj.getObj();
この例だとSのsetObj()にあるのはクラスBと決め打ちしてしまっているが、ここを動的に変えるような場合はpostparse()メソッドの中で引数valueの内容を判別して処理を分岐すればできるのではないかと思う。 もしかするとannotationとか使うともっと楽なやり方があるのかもしれないが、まだよくわかっていないので今後の課題。

0 件のコメント:

コメントを投稿