2013年7月23日火曜日

[Java]JSONをPOJOにバインドするとき、オリジナルの変換を行う

JSONからPOJO(Bean)に変換する処理はめんどくさいです。たとえば、
  • JSONは文字列で、Java側はEnum
このケースでは、文字列=Enumの定義名という式が成り立てば、自動で変換されます。下のstep1がそれに該当します。しかし文字列がenumと一致しない場合、正しく変換されません。おそらく初期値になってしまうでしょう。以下の例ではnullになってしまっています(enumはnullを許容するんです)。
次に、
  • JSON上は数値なんだけど、Java上はEnum
この場合、自動的な変換では明らかに無理です。こういう場合、JsonDeserializer<T>を使います。 以下の例では、数値をenumにしています。こちらも存在しない値の場合、nullを返却しています。 実装した例はこんな感じ。
public class GsonEnum {

 public static void main(String[] args) {
  Gson gson = new Gson();

  // タイプを取り出す(List)
  Type type = new TypeToken<List<Signal>>() {}.getType();

  // JSON->List<Signal>
  List<Signal> signal = gson.fromJson(new InputStreamReader(
    GsonMap.class.getResourceAsStream("signal2.json")), type);

  System.out.println("---step1---");
  System.out.println(signal);

  // JSON->List<Signal>
  // #1 TypeAdapterを設定したGsonを生成する
  Gson gson2 = new GsonBuilder().registerTypeAdapter(SignalStat.class,
    new SignalStatDeserializer()).create();
  List<Signal> signal2 = gson2.fromJson(new InputStreamReader(
    GsonMap.class.getResourceAsStream("signal3.json")), type);

  System.out.println("---step2---");
  System.out.println(signal2);
 }

 /**
  * 解析結果を受け取るクラス
  */
 static class Signal {
  private SignalStat stat;
  private String message;
  private SignalStat next;

  @Override
  public String toString() {
   StringBuffer sb = new StringBuffer();
   sb.append("[");
   sb.append("stat=").append(stat);
   sb.append(" message=").append(message);
   sb.append(" next=").append(next);
   sb.append("]");

   return new String(sb);
  }
 }

 /**
  * ステータスの表現.
  */
 enum SignalStat {
  Red, Yellow, Blue;

  // #2 変換用のMAP(int -> SignalStat)
  static Map<Integer, SignalStat> CONVERTER = new HashMap<Integer, SignalStat>();
  static {
   for (SignalStat stat : SignalStat.values()) {
    CONVERTER.put(stat.ordinal(), stat);
   }
  }
 }

 /**
  * SignalStat用Deserializer.
  */
 static private class SignalStatDeserializer implements
   JsonDeserializer<SignalStat> {

  @Override
  public SignalStat deserialize(JsonElement json, Type typeOfT,
    JsonDeserializationContext context) throws JsonParseException {
   // #3 int -> SignalStat
   return SignalStat.CONVERTER.get(json.getAsInt());
  }
 }
}
ポイントは以下の通り
  • #1:TypeAdapterを設定したGsonを生成するためにGsonBuilderをnewしregisterTypeAdapterでDeserializerを指定する
  • #2:enumの中に、変換用Mapを用意しておく。staticでOKです
  • #3:JsonDeserializerを実装する。#2の変換用Mapを使ってint->enumを実現している
入力データはこんな感じ(signal2.json)
[
    {
        "stat": "Red",
        "message": "とまれ",
        "next": "Blue"
    },
    {
        "stat": "Yellow",
        "message": "とまっとけ",
        "next": "Red"
    },
    {
        "stat": "Invalid",
        "message": "すすんでもいいよ",
        "next": "Yellow"
    }
]
入力ry (signal3.json)
[
    {
        "stat": 0,
        "message": "とまれ",
        "next": 2
    },
    {
        "stat": 1,
        "message": "とまっとけ",
        "next": 0
    },
    {
        "stat": 100,
        "message": "すすんでもいいよ",
        "next": 1
    }
]
出力結果はこんな感じ
step1ではInvalidは存在しないのでnullとなり、step2では100は存在しないのでnullになっています。
---step1---
[[stat=Red message=とまれ next=Blue], [stat=Yellow message=とまっとけ next=Red], [stat=null message=すすんでもいいよ next=Yellow]]
---step2---
[[stat=Red message=とまれ next=Blue], [stat=Yellow message=とまっとけ next=Red], [stat=null message=すすんでもいいよ next=Yellow]]

0 件のコメント :