dankogai作のAWS APIからJSONの仕組みを理解する(1/2)

はじめに

前回、ヤマト運輸の配送状況を確認勝手APIを作成してxml,yaml,jsonの形式でデータ取得できるようになったのですが、まだJSONPは未対応でした。
[WebAPI]ヤマト運輸の配送状況を確認するAPIを作ってみた

で、以下のページをチェックしてみると、dankogaiがAmazonの商品情報取得のAPIを作成&紹介しているのですが、xml/yamlに加えてJSONPも対応しているようです。しかもソースも公開してくれています。
AWS Caching Proxy w/ Authentication Support


今回はdankogaiのサイトを見ながらJSONPの仕組みを理解&うちのAPIをJSONP対応してみます。



そもそもJSONPってなに?


Wikipediaより

JSONP(JSON with padding)とは、scriptタグを使用してクロスドメインなデータを取得する仕組みのことである。HTMLのscriptタグ、JavaScript(関数)、JSONを組み合わせて実現される。

…(中略)

JSONPでは、通常、上記src属性のレスポンスの内容はjavascript関数呼び出しの形式となるため、src属性に指定するURLにその関数の名前をクエリ文字列の形式で付加する。一般的な方法では、この時に指定する関数名はWebページ側ですでに定義されているコールバック用の関数の名前になる。


一言でいうと、JSONは単なるデータ形式だけど、JSONPはデータ取得後に呼び元が指定した関数を呼んでくれるって仕組みらしい。


とりあえず、お手本のソースを見てみた。
が、JavaScriptは昔の知識+雑誌で最近の仕組みをチラ読みなレベルで止まっているので、さっぱり分からない…

とりあえず、提示されているHTML Source:と、JS Source:を1ファイルにコピーしてローカルにhtmlファイルを作ったら動作したので、ちょっとづつ改造して仕組みを理解してみた。
※もちろん、コピーする際にJavaScriptはscriptタグでくくりますよ。


まずは大枠をつかんでみる


まず、htmlの方は大した内容じゃないのでスルーします。

で、javascriptを見ると80行ぐらいしかないけど、構造がさっぱり分からない。
なので、まずは大枠だけ見てみる。

(function(d){
 ...
})(document);



かなり削った…
ググったところ、この表記は以下の処理と(ほぼ)等価らしい。

func = function(d){
 ...
}
func(document);



でもって,これは…

function func(d){
 ...
}
func(document);


と等価。
実際に上記のように書き換えて、スクリプトが動作することを確認した。

ここまでくると、関数を1つ作ってそれをコールしているだけということが分かる。
括弧でくくっているあたりは、たぶんスコープとかネームスペースがらみだと思うけど、細かいことは後で調べるとして、次は関数内を見て行く。





初期処理の仕組みを確認


大枠が把握できたので、関数内の構造をざっくり把握する。

function(d){
    var $         = ...;
    var json2list = ...;
    JSONP         = ...;
 
    (function(f){
      if (1 /*@cc_on -1 @*/){ /* good */
        window.setTimeout(f, 100)
      }else{ /* evil */
        window.attachEvent('onload', f)
      }
    })(
      function(){
        JSONP.get($('asin').value);
        $('demo.src')[ 
            d.body.innerText ? 'innerText' : 'textContent'
        ] = $('demo').innerHTML;
      }
    );
}



3つの変数代入と、なんか処理が書いてある。
最初の3つは置いといて、最後の処理を見てみる。

これは、最初見た大枠と同じ構成なので…

(function(f){
  if (1 /*@cc_on -1 @*/){ /* good */
    window.setTimeout(f, 100)
  }else{ /* evil */
    window.attachEvent('onload', f)
  }
})(
  function(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
  }
);



以下の形に書き換え可能だ。

function func2(f){
  if (1 /*@cc_on -1 @*/){ /* good */
    window.setTimeout(f, 100)
  }else{ /* evil */
    window.attachEvent('onload', f)
  }
})
 
func2( function(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
});




でもって、さらに書き換えると、以下の形式になる。

function func2(f){
  if (1 /*@cc_on -1 @*/){ /* good */
    window.setTimeout(f, 100)
  }else{ /* evil */
    window.attachEvent('onload', f)
  }
})
 
function func3(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
}
 
func2( func3() );




さらにさらに書き換えて…

 
if (1 /*@cc_on -1 @*/){ /* good */
    window.setTimeout(func3, 100)
}else{ /* evil */
    window.attachEvent('onload', func3)
}
 
 
function func3(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
}



上記の形に持ってこれた。

この中で、”/*@cc_on -1 @*/”が不明だったので調べてみると、IE用の条件付きコンパイル構文らしい
@cc_on ステートメント

この仕組みで、IE以外の時は(条件式が1なので)if側、IEの時はelse側に行かせるよう、クライアントによって分岐させている。


なので、仮にIE側だけで考えると、以下のようになる。

 
window.attachEvent('onload', func3)
 
function func3(){
    JSONP.get($('asin').value);
    $('demo.src')[ 
        d.body.innerText ? 'innerText' : 'textContent'
    ] = $('demo').innerHTML;
}




画面が開いたときにfunc3の処理をコールしているだけだ。
これによって、画面が開いたタイミングで書籍情報が表示されるようになっている。
JSONPとは直接関係無さそうなので、本関数についてはこれ以上の深追いはやめとく。


変数$とjson2listを確認する


次は、さっきと飛ばした3つの変数のうち、簡単そうな$とjson2listをチェックする。

function(d){
    var $         = ...;
    var json2list = ...;
    JSONP         = ...;
}



ます最初の$は本当に簡単。

var $ = function(id){ return d.getElementById(id) };



コレは単にd.getElementById(x)を、$(x)って書きたい為のalias。
毎回d.getElementById()と書くと長いから省略してるだけだ。

試しにスクリプト中の$(x)を、d.getElementById(x)に置き換えても正しく動作する。
さらに、dは、documentなので、document.getElementById()と書き換えても動く。




次にjson2list。
これも書き換えてしまうと以下の構造になっていて、何らかの形で取得したJSONの構造体をhtmlのelementに置換する、名称どおりの関数。

function json2list(json){
    ...
    return ul; //または,return ol;
}



これはこれで興味があるけど、今回知りたい”JSONの仕組み”からは少し外れるし、見た感じで大体分かるので、詳細のチェックはスルーしておく。


で、残りのJSONP変数は…

そして、ついに本命の処理っぽいJSONP変数の定義を確認する。

構造は以下のような感じになっているのだけど…

JSONP = {
  get:function(asin){ ... },
  run:function(json){ ... }
};



この構文が分からないので、ちょっと調べてみる.




検索すると、JavaScript の配列と連想配列の違い に情報が載ってた。


JSONPは連想配列(hash)で、keyがgetとrun、valueがそれぞれ関数になっている。

ということは、該当のコードは単なるハッシュなので、以下のように書き換え可能となるはず。

JSONP = new Array();
JSONP[ "get" ] = function(asin){
    var u = 'http://api.dan.co.jp/asin/' + asin + '/JSONP.run';
    var s = d.createElement('script');
    s.charset = 'UTF-8';
    s.id = s.src = u;
    d.body.appendChild(s);
};
 
JSONP[ "run" ] = function(json){
    $('amazon.attr').innerHTML = '';
    if (json.Error){
        $('amazon.attr').appendChild(json2list(json));
        return;
    }
    $('amazon.link').href = 'http://www.amazon.co.jp/gp/product/' + json.ASIN
      + '?ie=UTF8&linkCode=as2tag=blogsofdankog-22';
    if (json.ImageSets){
       var imageset = json.ImageSets.ImageSet;
       if (imageset.length) imageset = imageset[0];
       $('amazon.img').src = imageset.MediumImage.URL;
    }else{
       $('amazon.img').src = 'http://ec1.images-amazon.com/'
         + 'images/G/09/nav2/dp/no-image-no-ciu._AA128_.gif';
    }
    $('amazon.attr').appendChild(json2list(json.ItemAttributes));
};


試しに書き換えてみたら問題なく動作した。



で、作ったハッシュのvalueには、ピリオドでアクセス出来る(JSONP.getのような感じ)

そういえばさっき端折った画面ロード時の処理に、以下の記述があった。
これは、JSONP連想配列内のkey”get”に対応づく関数をコールしているということになる。

JSONP.get($('asin').value);



こちらも連想配列形式でいけるはずなので、以下のように書き換えてもOKだった。
認識は間違ってないようだ。

JSONP[ "get" ]( $('asin').value );






で、最初に戻ると…


htmlのソースに戻ると、ボタンのクリックで今調べたJSONP.get()をコールしている。

JSONP.get(this.previousSibling.value)



これでやっと、スクリプトのつくりと、エントリポイントが理解できた。

次回は、本来の目的だった”JSONの仕組み”を確認します。
dankogai作のAWS APIからJSONの仕組みを理解する(2/2)

関連記事

コメントを残す

メールアドレスが公開されることはありません。