Android
GSON
Retrofit

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

More than 1 year has passed since last update.

※初投稿は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

この辺ってどれか一個あればあとは同じ形なので

一気通貫にコード生成してくれるツールってないんですかね?