※初投稿は2015年05月20日 14時21分 (JST)です。
2017年現在では通用しない可能性があります。
ご了承ください。
WebAPIからJSONを取得してモデル(POJO)にパースするというよくある処理を実装しようとしてRetrofitを使っていたのですが
APIが返すJSONが一曲あってそのままでは使えなかったのでGSONにパースするための方法を調べました。
すんなりパースできないJSON
- JSONに不要なメタデータが付帯している
- 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"
}
-
ほしいのはitemsの持っている商品情報のみ
-
itemsの中にも更に階層がある。(今回はtargetがItemクラスに相当)
ちょっと特殊なケースな気もします。 -
prices(商品の価格)
通常時、割引時、など場合によって値が増減する場合
##パースしたいモデル
JSON→POJOのコード生成にはjsonschema2pojoを使いました。(http://www.jsonschema2pojo.org/)
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を拡張します。
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
この辺ってどれか一個あればあとは同じ形なので
一気通貫にコード生成してくれるツールってないんですかね?