理系学生日記

おまえはいつまで学生気分なのか

WebService::Simple ではてなブックマークエントリー情報取得 API を呼び出す方法

はてなブックマークエントリー取得 API は、JSON で結果を返却します。
そのため、以下のようなパラメータをコンストラクタを渡せば、一件問題がないように見えます。

my $ws = WebService::Simple->new(
    base_url        => 'http://b.hatena.ne.jp/entry/jsonlite/',
    response_parser => 'JSON',
);

しかし、当該の API は、「誰もブックマークしていない」URL に対しては JSON のオブジェクトではなく、"null" という文字列を返却してきます。
上記の形で生成した WebService::Simple オブジェクトは、この "null" を正しくパースできず、"JSON text must be an object or array (but found number, string, true, false or null, use allow_nonref to allow this) " というエラーを吐いてしまいます。

これは、WebService::Simple::Parser::JSON が内部で使用する JSON モジュールは、"JSON text" のパースを行うためです。JSON text は RFC4627 上では object または array として定義されており、いわゆる文字列は JSON text の範疇には含まれません。結果として JSON モジュールがパースに失敗し、上記のエラーメッセージを吐いています。
実際に、JSON.pm の POD にも以下の記述があります。

There is no guessing, no generating of illegal JSON texts by
default, and only JSON is accepted as input by default (the latter
is a security feature).

では、JSON モジュールにどうやって "null" をパースさせるようにすれば良いかというと、エラーメッセージに書いてある通り、allow_nonref を呼び出してやれば良いです。

my $json = JSON->new->allow_nonref

これによって、$json は null をパースできるようになります。
次の問題は、"null" をパースできるようになった上記の $json を、パーサとして WebService::Simple に使わせるにはどうすれば良いかという問題です。

結論から言うと、以下の方法で良いです。

my $ws = WebService::Simple->new(
    base_url        => 'http://b.hatena.ne.jp/entry/jsonlite/',
    response_parser => WebService::Simple::Parser::JSON->new(json => JSON->new->allow_nonref),
);

WebService::Simple の response_parser パラメータには、WebService::Simple::Parser の子クラスのオブジェクトを直接渡すことが可能です。また、WebService::Simple::Parser::JSON のコンストラクタは、引数として json パラメータを取ることができ、そこに JSON モジュールのオブジェクトを渡せば、内部で当該のオブジェクトが使用される作りになっています。従って、上記の形で WebService::Simple オブジェクトを作成すれば、"null" が返却されてもエラーとなることはなくなります。

あとは、以下のようなかんじでよいのではないでしょうか。エラー処理は別途必要ですが。

my $res   = $ws->get({ url => $url })->parse_response;

まぁそのまえに、

  • GET で叩くだけなんだから UserAgent とか Furl とかそのまま使えばよくね?
  • content が "null" かどうかをチェックした後でパースするようにすれば良くね?

という話はある。