LoginSignup
29
30

More than 5 years have passed since last update.

Retrofitを使ってややこしいJSONをモデル(POJO)にパースする

Last updated at Posted at 2015-05-20

※初投稿は2015年05月20日 14時21分 (JST)です。
2017年現在では通用しない可能性があります。
ご了承ください。

WebAPIからJSONを取得してモデル(POJO)にパースするというよくある処理を実装しようとしてRetrofitを使っていたのですが
APIが返すJSONが一曲あってそのままでは使えなかったのでGSONにパースするための方法を調べました。

すんなりパースできないJSON

  1. JSONに不要なメタデータが付帯している
  2. JSONに階層がある
  3. キーが不定なフィールドがある
item.json
{
  "items": [
    {
      "id": "0001",
      "target": {
        "name":"車",
        "color":"red",
        "prices":{
          "default":1000
        }
      },
      "updated": "2015/01/01"
    },
    {
      "id": "0002",
      "target": {
        "name":"自転車",
        "color":"blue",
        "prices":{
          "default":1000,
          "sale":800  //データが可変
        }
      },
      "updated": "2015/01/02"
    }
  ],
  "meta": "hoge",
  "data": "huga",
  "timestamp": "2015/05/08 17:26:55"
}
  1. ほしいのはitemsの持っている商品情報のみ

  2. itemsの中にも更に階層がある。(今回はtargetがItemクラスに相当)
    ちょっと特殊なケースな気もします。

  3. prices(商品の価格)
      通常時、割引時、など場合によって値が増減する場合

パースしたいモデル

JSON→POJOのコード生成にはjsonschema2pojoを使いました。(http://www.jsonschema2pojo.org/)

model.java

public class Item {

    @Expose
    private String name;

    @Expose
    private String color;

    @Expose
    private HashMap<String, Long> prices;

    // Serialize/Deserialize不要のフィールドは@Exposeアノテーションを付けない
    private String notExpose; 

//  getter/setter...

}

3.jsonschema2pojoを使うとPricesクラスを作ってしまうので困ってたのですが、単に型をマップにしてあげるだけでした。

TypeAdapterをカスタマイズする。

シリアライズしたいノードはtargetなので、
対象のノードまで辿ってパースできるようにTypeAdapterFactoryを拡張します。

ItemAdapterFactory.java
public class ItemAdapterFactory implements TypeAdapterFactory{

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> delegate          = gson.getDelegateAdapter(this, type);
        final TypeAdapter<JsonElement> adapter = gson.getAdapter(JsonElement.class);

        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            @Override
            public T read(JsonReader in) throws IOException {
                // in → JsonTreeReader:JSONを再帰的にリーフまで読みこむ。

                JsonElement element = getTargetElement(adapter.read(in));

                return delegate.fromJsonTree(element);
            }
        };
    }

    /* 対象ノードを探す */
    private JsonElement getTargetElement(JsonElement element) {

        if(!element.isJsonObject()) return element;

        JsonObject obj = element.getAsJsonObject();

        /* targetにたどり着くまで必要なnodeを教えてあげる */
       if (obj.has("items")) {
            return obj.get("items");
       } else if (obj.has("target")) {
            return obj.get("target");
       }
    }
}

呼び出し側

  public Observable<List<Item>> getItemObs(){

        Gson gson = new GsonBuilder()
                .excludeFieldsWithoutExposeAnnotation()
                .registerTypeAdapterFactory(new ItemAdapterFactory())
                .create();

        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint("http://hogehoge")
                .setConverter(new GsonConverter(gson))
                .build();

        ItemService service = restAdapter.create(ItemService.class);

        return  service.obsItemList();
    }

参考URL

Retrofitでニコニコ動画APIのレスポンスをそのままではパースできない - Qiita

備考

JSON ←→ POJO ←→ SQLite/ActiveAndroid/Realm
この辺ってどれか一個あればあとは同じ形なので
一気通貫にコード生成してくれるツールってないんですかね?

29
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
30