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 件のコメント: