入門 JSON

no extension

ここではあまりプログラミングの話はしないのですが(私も今気がついた), たまにはいいでしょう。 今回は JSON というデータフォーマットのお話です。 めっさ長文です。 ご注意を。 (3/8 追記があります)

最近 JSON (JavaScript Object Notation)にハマってます。 JSON というのはごく軽量のデータフォーマットで, Javascript (というより ECMAScript と言うべきかもしれませんが)の言語仕様がベースになっています。 とはいえ, JSON 自体は Javascript からは独立していますので他の言語(C/C++, Java, C#, Perl, Ruby, Python など)でも問題なく扱うことができます。 JSON は以下の2種類のデータ構造の組み合わせでできています。 (JSON フォーマットの詳しい解説をご所望の方は「入門 JSON 2」を参照ください)

  • 「名前:値」の組み合わせ(連想配列)。組み合わせ自体をひとつの要素として扱うことができます。
  • 要素の順序つきリスト(配列)。リスト全体をひとつの要素として扱うことができます。

例えば私についての情報を JSON で作成するとしましょう。 まず私の名前は連想配列を使って

{ "name" : "Yasuhiro ARAKAWA" }

といった具合に表現できます (「名前:値」の名前の部分は必ず文字列である必要があるのでクォーテーションで囲みます。私はここでハマりました)。 ついでにニックネームも追加しましょうか。 データ全体をあらわす名前も付けますしょう。

{
  "Person" : {
    "name" : "Yasuhiro ARAKAWA",
    "nick" : "Spiegel"
  }
}

更に最近注目しているサイトなんかも入れてみましょう。

{
  "Person" : {
    "name" : "Yasuhiro ARAKAWA",
    "nick" : "Spiegel",
    "interest" : [
      { "title" : "SETI@home", "url" : "http://setiathome.ssl.berkeley.edu/" },
      { "title" : "Flickr", "url" : "https://www.flickr.com/" }
    ]
  }
}

何かに似てきました。 そう FoaF です(というか,似るように要素を追加していったのですが)。 JSON の記法は RDF の Turtle に似ているそうで, RDF への応用を考えている方もいらっしゃいます。

JSON は(成り立ちから考えても当然ですが) Javascript と非常に相性の良いフォーマットです。 JSON のデータを丸ごと Javascript の eval メソッドに通すと構造ごとオブジェクトができてしまいます (実際にはそのまま eval メソッドに通すのはセキュリティ上危険なので, JSON.parse などのパーサを通すことが推奨されています)。 Javasript と相性がいいということは Web ブラウザ(クライアント側)で操作がしやすいということです。 そこで当然 Ajax と組み合わせたアプリケーションが色々と思いつくわけですが, そうなると XML と JSON との相互変換が重要になってきます。 XML と JSON との相互変換, 特に XML から JSON への変換を行うライブラリは既にネットに色々あるようです。 Javascript で動作するものでは以下のライブラリがいい感じです。

Javascript でリモートのデータを取得するには XMLHttpRequest クラスを使うのですが, XMLHttpRequest ではセキュリティ上の制約から他所のドメインにアクセスできないようになっています(「クロスドメインの制約」と言います)。 他所のドメインから XML や JSON データを取得するには大きく2つの方法があります。 ひとつは, いったんサーバ側でデータを取得してからクライアントに渡す方法(CGI などのサーバサイド・スクリプトが必要です)。 もうひとつは Javascript ソースとしてインクルードする方法です。 del.icio.us では JSON データのフィードに後者の方法を使っています。 この方法はとても便利なのですが, JSON データを無条件に受け入れてしまうためセキュリティ上の危険を意識する必要があります。

前者の方法は(サーバ側で適切なパーサを通せば)後者よりもいくらかマシです。 そこで, 前者の方法を試してみることにします。 BOINC が提供しているクライアントのバージョン情報(XML 形式)をサーバ側で取得し, JSON 形式に変換してクライアントに渡します。 サーバ側のスクリプトには以下を使わせていただきました。

このスクリプトは Perl の Data::Dumper が JSON の形式に似ていることを利用して効率的に変換を行っています。 ただ, これをこのまま使うとオープンプロキシのようになってしまうので, 特定 URL の情報のみを取得するように改造しました。 このスクリプトで取得した JSON データを「Club-HUAA サポートページ」で受けてクライアント側で表示します。 クライアント側の Javascript は以下のような感じです。

<div id="versiondata">読み込み中...</div><script>
window.onload = function(){
    var s = document.getElementsByTagName("head")[0].appendChild(document.createElement("script"));
    s.type = "text/javascript";
    s.charset = "utf-8";
    s.src = "http://huaa.baldanders.info/xml/?type=client";
}
var client = {};
client.onload = function(data){
    var src = "<div><table>";
    var platform = "";
    for (i = 0; i < data.versions.version.length; ++i) {
        var item = data.versions.version[i]
        if( item.platform != platform ) {
            src += "<tr><th colspan=\"4\">" + item.platform + "</th></tr>";
            platform = item.platform;
        }
        src += "<tr>"
        src += "<td><a href=\"" + item.url + "\">" + item.description + " " + item.version_num + "</a></td>"
        src += "<td>" + item.installer + "</td>"
        src += "<td>" + item.size_mb + " MB</td>"
        src += "<td>" + item.date + "</td>"
        src += "</tr>"
    }
    src += "</table></div>";
    document.getElementById("versiondata").innerHTML = src;
}
</script>

結果についてはテストページを参照してください。 CGI パラメータ以外にも若干修正しています。 取得したデータは一定時間キャッシュするようになっているのですが, キャッシュの時間を10分から6時間に変更しました。 また XMLin で XML データを Perl の内部表現に変換する際に XML のルート要素が欠落する問題があったため, 該当部分を以下のように変更しています。

my $xmlobj = XMLin($res->content);
              ↓
my $xmlobj = XMLin($res->content, KeepRoot => 1);

最終的なスクリプトを xml2json.txt に公開しています。 また CGI の機能については「JSON 変換サービス」を参考にしてください。 (順次機能を増やしていく予定です)

クライアント側で簡単にデータを操作できるというのはサービスのユーザにとって大きなメリットがあります。 一方でセキュリティ上の問題も見逃せないところです。 これからも JSON に注目していきたいと思います。