この記事はあすとろなんとか@astronaughts さん主催のTitanium Mobile Advent Calendar 2012 向けに書いています。
TitaniumAdvent Calendarもう全部埋まったと思い込んでいたら明日と明後日が空いてるのにちょっとビックリ。「WebAPIにアクセスして取得できるJSON処理についての3つのTIPS」というネタ書こうと思っていたけどTIPが2個しかないからなぁ。。
— hiroshi oyamada (@h5y1m141) December 18, 2012
というのが背景にあって、書いてみました。
本題
Twitterに代表されるように最近のWebサービスではそのサービスのリソースにアクセスできるようにAPIが提供されており、WebAPIを活用したiPhoneアプリ開発をすることが比較的多くあるかと思います。
WebAPIを通じて取得したJSONオブジェクトの処理として
- JSONオブジェクト内の特定のプロパティにマッチする値だけ取り出す
- 最初に読み込んだJSONと、新しく読み込んだJSONを結合する
- WebAPIを通じて取得するサーバサイドのJSONデータと、ローカルにキャッシュしたJSONデータの同期
というケースがあるかと思い、この3点についてのTitaniumでの実装方法についてまとめてみました
JSONオブジェクト内の特定のプロパティにマッチする値だけ取り出す
WebAPIを通じて以下のようなJSONを取得している状態だとして、特定のタグにマッチする(以下JSONでの例だと、tagsのnameプロパティがJavaScriptのもの)ものを取得する処理は意外とよくある状況かと思います
var items = [ {"uuid":"88194612bb0fa705e39d","tags":[{"name":"JavaScript","url_name":"JavaScript","icon_url":"http://qiita.com/system/tags/icons/000/000/015/medium/images.jpeg?1316130454","versions":[]},{"name":"Coffescript","url_name":"Coffescript","icon_url":"http://qiita.com/icons/medium/missing.png","versions":[]},{"name":"Qiita","url_name":"Qiita","icon_url":"http://qiita.com/system/tags/icons/000/000/001/medium/favicon.png?1320171109","versions":[]}]}, {"uuid":"542e652b73a359a1d118","tags":[{"name":"Zsh","url_name":"Zsh","icon_url":"http://qiita.com/system/tags/icons/000/000/041/medium/zsh.jpg?1319819723","versions":[]},{"name":"Github","url_name":"Github","icon_url":"http://qiita.com/system/tags/icons/000/000/090/medium/Octocat.jpg?1320170917","versions":[]}]} ];
Underscore.jsのwhere()を利用することでスッキリと書くことができます。具体的にはMinifiedされたものををダウンロードして、Titanium のプロジェクトフォルダのResouces直下に配置した上で、下記コードで実現できます。
var _ = require("lib/underscore-min"); var result = [ {"uuid":"88194612bb0fa705e39d","tags":[{"name":"JavaScript","url_name":"JavaScript","icon_url":"http://qiita.com/system/tags/icons/000/000/015/medium/images.jpeg?1316130454","versions":[]},{"name":"Coffescript","url_name":"Coffescript","icon_url":"http://qiita.com/icons/medium/missing.png","versions":[]},{"name":"Qiita","url_name":"Qiita","icon_url":"http://qiita.com/system/tags/icons/000/000/001/medium/favicon.png?1320171109","versions":[]}]}, {"uuid":"542e652b73a359a1d118","tags":[{"name":"Zsh","url_name":"Zsh","icon_url":"http://qiita.com/system/tags/icons/000/000/041/medium/zsh.jpg?1319819723","versions":[]},{"name":"Github","url_name":"Github","icon_url":"http://qiita.com/system/tags/icons/000/000/090/medium/Octocat.jpg?1320170917","versions":[]}]} ]; matchTag(result,"JavaScript"); function matchTag(items, tagName) { var i, tags, value, _, _i, _ref; for (i = _i = 0, _ref = items.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) { tags = items[i].tags; _ = require("lib/underscore-min"); value = _.where(tags, { "url_name": tagName }); } };
最初に読み込んだJSONと、新しく読み込んだJSONを結合する
以下のようなlatestEntries(WebAPIにアクセスして最新情報について取得したJSON)とolderEntries(最新投稿よりも1つ前のデータをWebAPIにアクセスして取得したJSON)を結合したいケースというのが意外とあるかなと思います。
latestEntriesのデータ構造例
var latestEntries = [ { "id":10438, "uuid":"7293065b838391c4c718", "user":{ "name":"Gen Tamura", "url_name":"GenTamura84", "profile_image_url":"https://si0.twimg.com/profile_images/2378845729/j5hs3slgh54kg6fkpqnp_normal.png" } }, { "id":10437, "uuid":"211f703949e160fa3012", "user":{ "name":"jabaraster", "url_name":"jabaraster", "profile_image_url":"https://si0.twimg.com/profile_images/1272321155/duke_globe_normal.gif" } } ];
olderEntriesのデータ構造例
var olderEntries = [ { "id":10436, "uuid":"397cb77652223b0a1348", "user":{ "name":"jabaraster", "url_name":"jabaraster", "profile_image_url":"https://si0.twimg.com/profile_images/1272321155/duke_globe_normal.gif" } }, { "id":10434, "uuid":"f5cf6c83fcfb5dd24c2d", "user":{ "name":"Shuhei Kagawa", "url_name":"shuhei@github", "profile_image_url":"https://secure.gravatar.com/avatar/ca0ab6e450f894e06652ee257df9d647?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" } } ];
latestEntriesと olderEntriesを結合する実装方法ですが、Underscore.jsの_.zip()使えばと思ったのですが、zip()は配列に対する処理で、こういうJSONオブジェクトの合成ができるわけでもないし、Underscore.jsではダメっぽい。(*1)
やりたいことはシンプルなのに、意外と難しいなぁと思っていたらAppceleratorのQ&AにMerge two json objects dynamically(android)というものがあるのを見つけました。
こんな感じにすることで、無事結合できました
var obj1 = JSON.stringify(latestEntries); var obj2 = JSON.stringify(olderEntries); var o1 = obj1.substring(0, obj1.length - 1); var o2 = obj2.substring(1,obj2.length); var json = o1 + ','+o2; var result = JSON.parse(json);
WebAPIを通じて取得するサーバサイドのJSONデータと、ローカルにキャッシュしたJSONデータの同期
サーバサイドと、クライアントサイドの双方向の同期処理をしたいならJSONDBモジュールを使うのが手っ取り早く出来ると思います。
※実際、自分も以前作りかけたアプリではサーバサイドにMongoDBのホスティングサービスのmongolabを使う前提で色々作って、かなりJSONDBの恩恵を受けましたし。
ただ、WebAPIを通じて取得するサーバサイドのJSONデータをローカルにキャッシュする一方向の同期だとJSONDBはどうもしっくり来ない所がありました。
ここまで書いておいて何なのですが、1年前位からローカルへのキャッシュについてあれこれ試行錯誤して、実は未だに最適な解決方法というのを思いついてません・・
なので、3つ目のTIPSは無いんです・・ごめんなさい ^_^;
ただ、昨日、@yagi_さんから、
@ryugoo_ @h5y1m141 同期だったらCouchDBじゃない?
— 八木の野郎 (@yagi_) December 18, 2012
というmentionもらい、これまでMongoDB以外の選択肢を全然考えてなかったので、CouchDB 使って一方向の同期が出来るかどうか考えてみようかと思ってます。
一方向の同期は自分的には長年のテーマっていうのはおおげさですが、解決したい課題の1つなんで、うまくいったら、ブログにまとめようと思います
(*1)私の調べ方が悪いだけかもしれませんが、CoffeeScriptでUnderscore.jsと戯れるのエントリでも
Underscore.jsには、Objectから(1)キーだけを配列として取り出す.keysと、(2)値だけを配列として取り出す.valuesという関数があります。が、逆に、keysとvaluesからObjectを作成するような関数がありません。
という記載あったので、Underscore.js標準機能ではやっぱり無理なのかな・・