jQueryでJSONPを読み込んでいるライブラリがあったので、どんな動作をしてるのかと思って基本的なところから動作確認をしてみた。
まずは静的なJSONファイルを作成して読み込むところから始める。
静的JSONファイルの読み込み
たとえば、次のような書籍のデータをJSONで書いて、json拡張子で作成する。
拡張子をjsonにしておけば、Apacheが自動的に”application/json”のContent-Typeを出力してくれる。
book.json
{"title":"自由をつくる自在に生きる (集英社新書 520C) [新書]","author":"森 博嗣","ASIN":4087205207,"price":714,"url":"http://www.amazon.co.jp/exec/obidos/ASIN/4087205207"}
ここで注意するのは、JSONの書式では文字コードはUTF-8であることと、シングルクォートではなくダブルクォートを使用する必要があるということだ。
私も適当に書いていてシングルクォートを使用していたら、$.getJSONがエラーも発生せず、ただ読み込めないという現象にハマってしまった。
JavaScript Object Notation – Wikipedia
{name: "John Smith", age: 33}という表記は許されない。この後者の表記はJavaScriptのオブジェクトの表記法としては正しいが、JSONとしては不正な表記である。
コード
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>静的JSONの読み込み</title> <script src="jquery-1.8.3.min.js"></script> <script> $(function(){ $('#get_static_json').click(function(){ var url = 'book.json'; $.getJSON(url, function(data){ for(var key in data){ $('#static_result').append($('<p>').html(key +': '+ data[key])); } }); }); }); </script> </head> <body> <button id="get_static_json">JSON読み込み</button> <div id="static_result" style="border: 2px solid #595">[結果出力]</div> </body> <html>
デモ
ボタンをクリックするとJSONを読み込み出力してくれる。
解説
静的なファイルなので何度クリックしても同じ内容が追加されるだけの単純な動作をする。
Firebugで確認するとボタンをクリックするたびにGETしにいっていることが分かる。
動的JSONの読み込み
静的だとあまり動きがなくて面白くないので、PHPでJSONを出力するスクリプトを書いてそれを読み込んでみる。
読み込み側はほとんどコードに変わりはなくて、URLを”book.json”から”json.php”に変更したくらいが主な変更点だ。
JSON出力側のPHPスクリプトでは64個の文字からランダムに8個取り出したパスワードみたいな文字列を出力するものにしてみた。
そのまま、echoでJSONを出力するとContent-Typeが”text/html”で出力されてしまうので、header関数で”application/json; charset=UTF-8″を指定している。
コード
json.php
<?php $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; $text = ""; for($i=0; $i< 12; $i++){ $text .= $itoa64[rand(0, 63)]; } header("Content-Type: application/json; charset=UTF-8"); echo json_encode(array('text' => $text));
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>動的JSONの読み込み</title> <script src="jquery-1.8.3.min.js"></script> <script> $(function(){ $('#get_dynamic_json').click(function(){ var url = 'json.php?' + new Date().getTime(); $.getJSON(url, function(data){ for(var key in data){ $('#dynamic_result').append($('<p>').html(key +': '+ data[key])); } }); }); }); </script> </head> <body> <button id="get_dynamic_json">JSON読み込み</button> <div id="dynamic_result" style="border: 2px solid #595">[結果出力]</div> </body> <html>
デモ 動的JSONの読み込み
ボタンを押すたびに違う文字列が表示される。
解説
URLの?以降のパラメータはブラウザによってはキャッシュで同じ文字列が表示されたのでその対策で追加した。
var url = 'json.php?' + new Date().getTime();
これで、動的なJSONの出力と読み込みができるようになった。
ただ、静的なJSONも動的なJSONもどちらも同じドメイン内に設置されている必要があるので
別なドメインやサブドメインで運用されているAPIがJSONを出力していたとしてもそのまま使用することはできない。
そこで、JSONPの出番である。
JSONPの読み込み
JSONPを使えばscriptタグで外部JSファイルを読み込んでいることと同じになるので、別なドメイン、別なサブドメインからデータを読み込むことができる。
デモでは同じドメイン内で使用するけど、外部ドメインからでも使用できることを試してみるとよい。
コード
今度は現在のニューヨーク時間を取得するJSONPを出力してみた。
JSONPはJavaScriptなのでContent-typeは”text/javascript”とする。
JSONを出力するだけとは違ってcallback名のパラメータを受け取っているところが重要となる。
callbackが空の場合と変な文字列が入っていた場合は簡易的にdieで処理している。
jsonp.php
<?php header("Content-type: text/javascript"); if( !isset($_GET["callback"]) || $_GET["callback"] == "" || preg_match("/[^_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]/", $_GET["callback"]) ){ die('bad or missing callback'); } $callback = $_GET["callback"]; $time = new DateTime("now", new DateTimeZone('America/New_York')); $list = array( "atom" => $time->format(DateTime::ATOM), "timezone" => $time->getTimezone()->getName(), "year" => $time->format('Y'), "month" => $time->format('m'), "day" => $time->format('d'), "hour" => $time->format('H'), "minute" => $time->format('i'), "second" => $time->format('s'), ); echo $callback.'('.json_encode($list).')';
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JSONPの読み込み</title> <script src="jquery-1.8.3.min.js"></script> <script> $(function(){ $('#get_jsonp').click(function(){ var url = 'http://api.example.com/jsonp.php?callback=?'; $.getJSON(url, function(data){ for(var key in data){ $('#jsonp_result').append($('<p>').html(key +': '+ data[key])); } }); }); }); </script> </head> <body> <button id="get_jsonp">JSONP読み込み</button> <div id="jsonp_result" style="border: 2px solid #595">[結果出力]</div> </body> <html>
デモ
解説
JSONPになっても読み込み側もほとんど変わっていないけど、一番のミソはURLの指定で’callback=?’となっている部分だ。
$.getJSONにcallback名を指定する必要はない。
この’?’を部分をjQueryがユニークなcallback名に変換してくれる仕組みになっている。
jQueryのソースをのぞいてみるとcallbackは’jQuery’から始まりバージョン、ランダムな数字と時間から生成されていた。
jquery-1.8.3.js line: 1520-1522
// Unique for each copy of jQuery on the page // Non-digits removed to match rinlinejQuery expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
jquery-1.8.3.js line: 8161-8174
var oldCallbacks = [], rquestion = /\?/, rjsonp = /(=)\?(?=&|$)|\?\?/, nonce = jQuery.now(); // Default jsonp settings jQuery.ajaxSetup({ jsonp: "callback", jsonpCallback: function() { var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); this[ callback ] = true; return callback; } });
これで外部のドメインで運用されているAPIもJSONPに対応すれば読み込みできるようになった。
まとめ
JSONの書式でハマったように、一つ一つの動きを見ていくとより動作の仕様の理解が深まるなぁと。
あんまり、ライブラリや関数で処理することだけ考えていると基本的なところでハマったりするので、たまにでも基礎的なところを単純なところから押さえて行くことは十分ためになるよ、と。
参考記事
JSONがinvalidでもjQuery.getJSON()はエラーを吐かない | Like@Lunatic