JavaScriptでJSONをeval
JSON文字列をevalして値を取りたいときに挙動が妙ではまった、という話を聞いたので、Firefox1.5上のFireBugのコンソールで何パターンか試してみた。
{"key":"value"}というJSONデータが文字列で渡ってきて、それをevalしてJavaScriptの値として使いたい、という想定。
間違ったJSON文字列をevalした場合
>>> jsonstr = "{key:\"value\"}" "{key:"value"}" >>> var obj = eval(jsonstr) >>> obj "value" >>> typeof(obj) "string"
JSONのオブジェクトでは、ラベルも""でくくって文字列にしないといけないのだが、まずはそれを忘れた場合を試してみた。結果として返ってくるのは、なんとJSONのハッシュの要素にしたつもりの文字列のみ。
正しいJSON文字列をevalした場合
>>> jsonstr = "{\"key\":\"value\"}" "{"key":"value"}" >>> var obj = eval(jsonstr) invalid label {"key":"value"}
今度は、keyのほうもちゃんと""でくくってみた。ところが、evalした途端に invalid labelなる謎のエラーがでる。ぱっと見何が起ってるかさっぱりわからない。
代入文をeval
しょうがないので、"obj ="も文字列に含めてevalしてみる
>>> jsonstr = "{\"key\":\"value\"}" "{"key":"value"}" >>> eval("obj="+jsonstr) [object Object] >>> obj [object Object] >>> obj.key "value"
これはちゃんとObjectが返ってきてるし、ハッシュとしてアクセスもできる。成功。
JSON文字列を"()"でくくる
JSON文字列を()でくくってevalする。これはprototype.jsで使われている手法らしい。
>>> jsonstr = "{\"key\":\"value\"}" "{"key":"value"}" >>> obj = eval("("+jsonstr+")") [object Object] >>> obj.key "value"
これも成功。何故か、()でくくるだけで謎のinvalid labelエラーはでなくなる。
謎とき
結論としては、JSON文字列をevalするときは、代入文にするか全体を()でくくってevalしろ、ということになる。では何故最初の二つのケースでは失敗しているのか?特に、"invalid label"ってなにか?
JavaScriptの文法定義(http://www.mozilla-japan.org/js/language/grammar14.html)を見てみるとこの謎が解ける。JavaScript1.5にはラベル文(LabeledStatement)というものがあって、さらにラベル文のラベル部分は"Identifier :"と定義されているので、文字列はここには書けない。
構文やevalの仕様を全部ちゃんと読んでないので、ここから先は推測だけど、最初の二つのケースでは、evalする対象の文字列がJSON(Object Literal)として解釈されずに、Block("{}"でくくった文の集合)の中のラベル文(LabeledStatement)として解釈されているのだろう。
つまり、一番最初のケースでは、{key:...}がラベル文と解釈されて、かつ、key:はラベルとして適正なのでエラーもでない。ラベルと見なされたkey:の後に続く"value"が、このラベル文の値になるので、evalの結果として"value"という文字列だけが返ってくる。二番目のケースでは、{"key":..}がラベル文と解釈されて、かつ"key"がラベルとしては不適正なのでエラーがでる。これが"invalid label"の正体。
三番目では代入文に、四番目では"()"をつけたことによって、{"key":..}がラベル文ではなくJSON(Object Literal)として解釈され、期待通りの動作になる。
まー、prototype.jsみならって"()"つけとけってことかな。ラベル文の使い方については次に続く。
JavaScript1.5のラベル文
http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Guide:Loop_Statements:label_Statement
JavaScriptではソース中にラベルを記述しておくことで、breakやcontinueでの脱出時に、脱出範囲を指定できるようだ。知らなかった。
こんな風に使える
function label_test() { var i = 0; outer: // ラベルその1 for (var j = 0;j<10;j++){ inner: // ラベルその2 for (var k = 0;k<10;k++){ i++; while(1) { break; // break outer; break inner: } } } alert(i); }
10回ずつ回る二重の中でカウンタiをインクリメントして、最後にiの値をalertするコード。何もしないwhileの中でbreakしているだけなので、普通はbreakでwhile文から脱出するだけ。iは100になる。
ところが、"outer:"や"inner:"というふうにラベルを記述しておくと、breakをbreak outerもしくはbreak innerとラベルを指定することで、そのラベルのついた文から脱出することができる。
例えば、break outerと書くと、外側のforループからも脱出するのでiは一回しかインクリメントされず1が表示される。break innerでは内側のforループから脱出するので、10が表示される。
ちなみに、Firefox1.5で動作確認しました。Firefox1.5だとJavaScriptもバージョン1.5なんだな。Firefox1.5にのっかっているのは、JavaScriptバージョン1.6のようです。コメントありがとうございます。
追記:JavaScirptの仕様って規定されてるの?
ところで、(ECMAScriptでなくて)JavaScriptの仕様って誰が決めてるんだろ。Mozilla?
operaとかにのっかっているJavaScriptもMozilla(か誰か)の決めた仕様に従っているんだろうか?仕様からして違うんだろうか?それとも、処理系自体も使いまわしてるんだろうか?