JSONに関する注意点

職場で、JSONを返すAPIで値が無い/必要ないって時に何を返すべきかという話題がチラホラとあがります。
「trueとかってJSONなの?」って質問に自信満々で「はいJSONです!」「nullも文字列リテラルJSONです!」とおまけ付きで即答したものの、不安になってRFCにあたったら間違えてました。


http://www.ietf.org/rfc/rfc4627.txt
ここの 2. JSON Grammar のBNFを見ると

A JSON text is a serialized object or array.
JSON-text = object / array

ってハッキリ書いてあります。
nodeの JSON.parseとかがパースできちゃうので勘違いしてました。
http://jsonlint.com/
ここにnullとか入れても正しく失敗します。


その他、ありがちなミスについては
http://d.hatena.ne.jp/m-hiyama/20080728/1217205390
檜山さんトコを参照スべし。


しかしそれにしても、HTTPに乗ったJSON
Content-Length: 0
Content-Type: application/json
が正しいのか、正しい場合どう取り扱うべきなのかは謎のママ。

追記

多くのパーサーが対応していたり、Facebookなどの大きなサービスがboolean等を使っていたりする以上、拡張JSONとして認めた方がいいのかもしれません。

WebSocketをサクっと試すはずだった。

なのに

mod_pywebsocket 0.5.2 と Google Chrome 5.0.375.126 で echo_wsh が 動かない人(co-sche)へ。

# httpd.conf とか
PythonOption mod_pywebsocket.allow_draft75 On

これ、忘れてないっすか><

echo_client.pyなら動くのに…って時は要チェックです。

差分の内どこが原因なのかまでは確認出来ていないのですが、上記のバージョンの組み合わせだと

  • ChromeのWebSocket → draft75を元に実装
  • pywebsocket → それ以降のドラフトを元に実装

の違いによってデフォルトではハンドシェイクがウマくいかなくなっているみたいです。

最初っから、pydocちゃんと読めよ、俺…。

ちなみに

現時点でWebSocket Protocolの最新のドラフトはhttp://www.whatwg.org/specs/web-socket-protocol/にあります。

綺麗なバラにゃトゲがある

for (var i = 0, elem; elem = elems[i]; i++) { doSomething(elem) }って書き方見やすくていいな - こんにちはこんにちはmonmonです!で紹介されている記事に、ちょっとツッコミます。

元記事の紹介


ノードリストのイテレート - Google JavaScript Style Guide 和訳

var paragraphs = document.getElementsByTagName('p');
for (var i = 0, paragraph; paragraph = paragraphs[i]; i++) {
  doSomething(paragraph);
}

これはすべてのコレクション, 配列に対してうまく動きます. 要素がなくなるまでループし, 最後には false となりループが終了します.

落とし穴

代入文が値を返すことを利用した美しいコードで、getElementsByTagNameで取ってきたノードリストのイテレートには有用ですが、以下のような例では初っ端でコケちゃいます><

var list = [0, 1, 2, 3];
for (var i = 0, item; item = list[i]; i++) {
	doSomeThing(item);
}

なので…


Tips and Tricks - Google JavaScript Style Guide

This works well for all collections and arrays as long as the array does not contain things that are treated as boolean false.

↑の原文は
↓のように訳したほうがよさそうです。
[追記]現在は修正されています。コメント欄にも書いたように、本来は訳者のid:cou929_laさんにフィードバックを送るべきでした。[/追記]

これは、配列が真偽値のfalseとして扱われるものを含まない限り、全てのコレクションや配列に対してうまく働きます。

…って、原文にはちゃんと書いてありますね。

Scalaめっちゃ気持ちいい

ECMAScript4の頓挫以来探し求めていた、サクッと書けそうな言語にようやく出会えました。
Scalaです。

希望していた特徴

  • 関数が、第一級オブジェクト
  • 型注釈
  • スクリプトとして実行可能 (実際は、シングルトンオブジェクトにラップ→コンパイル→実行らしい)

おまけについてきて嬉しかった特徴

今ひとつな特徴

個人には非常に書きやすい言語なので、各OSネイティブの実装が出て来て(他力本願><)、Python並に普及してくれればとてもうれしいんですが…。
Programming in Scalaの邦訳書を購入したので、もうちょっと掘り下げて勉強してみます。

in_arrayのややこしい版

仕事で必要に駆られて書いてみました。

イメージとしては、タイトル通りin_arrayに複雑な条件を指定出来るようにしたもので、
第一引数の配列のネストレベルが深くなる毎に、and/orをトグルしながら検索していきます。

思い出しながらなので、こんなんだったかどうか微妙><

使い方

<?php
/* 検索条件 */
$needles = array(1, 2, array(3, array(4, 5)));

/* 検索対象を変えながらテスト */
echo in_array_ex($needles, array(1, 3, 2));	// true
echo in_array_ex($needles, array(1, 4, 2));	// false
echo in_array_ex($needles, array(4, 5, 1, 2));	// true

?>

ソースコード

<?php
function in_array_ex(Array $needles, Array $haystack, $logical_op = 1) {
	if (empty($needles)) return $logical_op;
	$needle = array_shift($needles);
	
	$tmp;
	if (is_scalar($needle)) $tmp = in_array($needle, $haystack);
	else if (is_array($needle)) $tmp = in_array_ex($needle, $haystack, !$logical_op);
	else throw new Exception(
		'The 1st argument must be an array of mixed in array, and scalar.'
	);
	
	if ($logical_op) return $tmp && in_array_ex($needles, $haystack, $logical_op);
	else return $tmp || in_array_ex($needles, $haystack, $logical_op);
}
?>

概要と使い道

眠いのでまた後日…><

phpの可変長引数についていろいろ

仕事でphpを書くようになって、今まで触れていた言語とのいろんなお作法の違いによく躓く毎日です><
結構長時間ハマった部分があったので、メモメモ。

今回は、phpで可変長引数を受け取ったり渡したりする方法について、JavaScriptとの対比で見ていきます。

先ずは、受け取る方から。

これはいろんなWebページで紹介されてますねー。
addAllは、引数の総和を返す関数です。

JavaScript
function addAll() {
	var result = 0;
	var args = arguments;
	var i = 0;
	var imax = args.length;
	for (; i < imax; i++) {
		result += args[i];
	}
	return result;
}

console.log(addAll(1, 2, 3, 4));	// output 10
php
<?php
function addAll() {
	$result = 0;
	$args = func_get_args();
	$i = 0;
	$imax = count($args);	// ((func_num_args()との違いを後で調べる。))
	for (; $i < $imax; $i++) {
		$result += $args[$i];
	}
	return $result;
}

print(addAll(1, 2, 3, 4));	// output 10
?>

ここで、これらの関数の引数は配列ではありません

次、渡す方いきます。

渡す方と言っても、関数呼ぶだけならそのまま

addAll(1, 2, 3, 4);

で終わってしまいますね><

ここでは自身も可変長引数を取り、内部でaddAllを呼ぶ関数を書いてみます。*1
wappedAddAllは、引数の総和を3乗して返す関数です。

JavaScript
function wrappedAddAll() {
	var args = arguments;
	var sum = addAll.apply(null, args);
	return Math.pow(sum, 3);
}

console.log(wrappedAddAll(1, 2, 3, 4));	// output 1000
php
<?php
function wrappedAddAll() {
	$args = func_get_args();
	$sum = call_user_func_array('addAll', $args);
	return pow($sum, 3);
}

print(wrappedAddAll(1, 2, 3, 4));	// output 1000
?>

これが出来るとなにが便利か。

実はこのcall_user_func_array、組み込み関数(ビルトインファンクション・定義済み関数)やクラスメソッド、インスタンスメソッドにも使えてしまうんです。

例えば、mysqli_stmt::bind_param(このメソッドは可変長引数を取ります。)をラップして、多次元配列を受け取ってプリペアドステートメントに順次投げるなんてこともできます。
PDO::executeみたいな。

自分は、この"user"に惑わされてスルーしてしまったが為に、かなりの時間をロスしてしまいました。
何故わざわざ"user"なのか、ご存知の方がいらっしゃいましたらコメント下さい><

*1:参照渡しだの値渡しだの参照の値渡しだの、applyはthisがナンダカンダだのというのは今回の趣旨から外れるのでここでは言及しません。

as3corelibのJSONと改行コード

as3corelibのJSONクラスを使うと、ActionScript 3.0からJSONを手軽に扱えます。
…と、得意げに言ってみましたが、実はハマりました><
タイトルにもある通り、URLLoader#load等で得た文字列をJSON.decodeする時、改行コードがLFでないとパースエラーに見舞われます。

[追記]

現在(as3corelib-.92以降)はCRでもLFでもCRLFでも大丈夫です。
id:FLIP_FLOPさんにコメントを頂き、再検証しました。

なので、以下はcorelib-.90以前のバージョンにのみ該当します。

[/追記]
[Fault] exception, information=Error: Unexpected 
 encountered
Fault, parseError() at JSONTokenizer.as:546

JSONLintもスンナリ通るし、おっかしーなーと思いつつ、ためしに空白文字・改行文字を削ってみると、
スペースなし → NG
タブなし → NG
改行なし → OK
という結果。
もしや…と.jsonファイルの改行コードをCRLFからLFに変えたところ、OKでした。

手書きのJSONはあまり扱わないかもしれませんが、ちょっとテストしてみたい時などには不便ですね。
それに、様々なWeb APIJSONを吐き出す昨今、コード中にCRLFが含まれないとも限りません。
そこで、取得した文字列を置換することで対応しました。

ちなみに、自分が読んだ限りでは、JSONの仕様には改行コードに関することは明記されていませんでした(文字列リテラル内のエスケープ以外)。

いつものようにコード例を添えて覚書

まずは.json
// something.json

{
	"propA" : [0, 1],
	"propB": [
		[2, 3, 4],
		[5, 6, 7],
		[8, 9, 10]
	]
}
そして、.as。
// Main.as

package {
   import com.adobe.serialization.json.JSON;
   import flash.display.Sprite;
   import flash.events.Event;
   import flash.net.URLLoader;
   import flash.net.URLRequest;

   public class Main extends Sprite {
      private var jsonURL:String = 'json/something.json';
      private var obj:Object = {};

      public function Main():void {
         var urlRequest:URLRequest = new URLRequest(jsonURL);
         var urlLoader:URLLoader = new URLLoader();
         urlLoader.addEventListener(Event.COMPLETE, completeHandler);
         urlLoader.load(urlRequest);
      }

      private function completeHandler(event:Event):void {
         event.target.removeEventListener(event.type, arguments.callee);
         var urlLoader:URLLoader = event.target as URLLoader;
         var jsonString:String = (urlLoader.data as String).replace(/\r\n|\r/g, '\n');
         obj = JSON.decode(jsonString);
         trace(obj.propB[1]);
      }
   }
}

一連の流れが見えるように、まるまる書いてみました。
ポイントは、completeHandler内のvar jsonStringの行です。
これで、.jsonの改行コードはCR、LF、CRLFのどれでもOKになります。
取り込んだ後でテキストとして扱う必要がないのなら、別に\nにしなくても空文字列でよいですね><

参考サイト

JSON
JSONの仕様。非常に短く簡潔です。ページの先頭で言語を選べます(日本語もある)。
JSONLint - The JSON Validator
そのまんま、JSONの構文チェッカーです。
もう一度、ちゃんとJSON入門 - 檜山正幸のキマイラ飼育記 (はてなBlog)
JSON仕様の、見落としがちな部分+αが丁寧にまとめられた記事です。
初めてJSONを出力する側に立った方は必読!
Google Code Archive - Long-term storage for Google Code Project Hosting.
as3corelibのプロジェクトページです。JSON周り以外にも、ユーティリティクラスが満載!