キマイラ・サイトは http://www.chimaira.org/です。
トラックバック/コメントは日付を気にせずにどうぞ。
連絡は hiyama{at}chimaira{dot}org へ。
蒸し返し歓迎!
ところで、アーカイブってけっこう便利ですよ。タクソノミーも作成中。
2008-07-28 (月)
もう一度、ちゃんとJSON入門
雑記/備忘 | |
僕自身も僕の周辺もJSONをよく使います。でも、細かい点でけっこうミスをやらかしています(苦笑)。このエントリーで、JSONを使う上で注意すべきこと/間違いやすい点をすべて列挙します。
内容 兼チェックリスト:
- 仕様原典さえ読めば完璧(のはずだが)
- 数値の前にゼロを付けてはいけない
- 16進数表記も禁止だよ
- 数値の前にプラスを付けてはいけない
- 小数点からはじまる数値はダメ
- 用語法が違うよ:プロパティとメンバー
- メンバー名には常に文字列を使う
- 空文字列""もメンバー名に使える
- 配列要素はキッチリと並べよう
- 文字列を囲むには二重引用符だけ
- 文字列内のエスケープが微妙に違う
- 仕様にないエスケープは構文エラー
- undefinedもNaNもありません
- ラッパーオブジェクトは使わないのが吉
- 型システムとtypeofに関する注意
- 最後に
仕様原典さえ読めば完璧(のはずだが)
JSONは、小さくて簡単な仕様です。次のWebページ(印刷して2ページくらい)にすべてが書いてあります。
これにちゃんと目を通せば、間違いなんて起きないはずなんですがねー。
でも実際には勘違い/間違いもありえるので、当エントリーで確認してくださいな。
数値の前にゼロを付けてはいけない
百二十三を表すのに、0123と書いてはいけません。JSON構文では、先頭の余分なゼロは許されていません。先頭ゼロを使うのは、0, 0.123, 0.1e12 のようなときだけです。
JavaScriptでは、0123と書いてもかまいません。が、これは百二十三じゃないですよ。やってみましょう。(実行例はRhino)
js> print(0123) 83 js>
先頭に0を付けると8進数になります。この記法はCの時代(もっと前?)からの伝統です。
#include <stdio.h> main() { printf("0123=%d 123=%04d\n", 0123, 123); }
これを実行すると、表示は次のようになります。
0123=83 123=0123
桁数を揃えるためのゼロパディングは、JSON構文としては全然ダメー!*1 意図した値とはまったく違った値として解釈されるよ。
この伝統的な8進表記法は間違いのもとだよね。という次第で、JavaScriptでも廃止が予定されています。(http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Guide:Literals より)
8 進整数リテラルは廃止予定であり、ECMA-262 第 3 版から除かれています。JavaScript 1.5 では依然として後方互換のためにそれをサポートしています。
16進数表記も禁止だよ
8進数はヤバイからやめたほうがいいけど、0x1A とかの16進数ならいいよね。ダメです。JSONでは十進だけです。
数値の前にプラスを付けてはいけない
JavaScriptでは(ほとんどのプログラミング言語で)、数値の前にはプラス(+)もマイナス(-)も付けられます。でもなぜか、JSONでは先頭のプラスを付けられません。マイナスだけです。
しかし紛らわしいことに、浮動小数点の指数部にはプラスを付けてもいいのです。+123はダメ、0.1e+12はOKです。
小数点からはじまる数値はダメ
JavaScriptでは、0.3333333333333333を.3333333333333333と書いてもかまいません。
js> 1/3 == .3333333333333333 true js>
しかし、JSONでは許されません。必ず0を付けてください(1個だけ!)。同様に、.1e12も認められません。
用語法が違うよ:プロパティとメンバー
JavaScriptでは、オブジェクトに含まれる名前・値ペア(name/value pair)をプロパティと呼びますが、JSONではメンバーです。プロパティ名に対応するJSON用語は特にないようですが、JSONの場合は名前(メンバー名)というよりはキー文字列というほうが適切でしょう。
それはなぜかというと、… 次の項目を読んでください。
メンバー名には常に文字列を使う
JSONオブジェクトのメンバーは名前・値ペアですが、名前も文字列リテラルです。つまり、二重引用符で囲む必要があります。
{x: 12, y: -3, color: "red"} はダメで、{"x": 12, "y": -3, "color": "red"} となります。キーが文字列に限定されたハッシュマップと考えるとよいと思います。
空文字列""もメンバー名に使える
空文字列""も文字列の一種です。JSONのオブジェクトでは、任意の文字列がメンバー名(キー文字列)に使えるので、{"": "hello"} は合法的データです。
JavaScriptで var x = {"": "hello"}; としたとき、ドット記法では名前(キー)が""であるメンバーにアクセスできないので、x[""] とする必要があります。
配列要素はキッチリと並べよう
JSON配列の構文は、基本的にはJavaScriptと同じです。ですが、次はダメです。
[, "Hello", , "World", ,]
当たり前だろうって? いやっ、JavaScriptだと上の書き方が許されちゃうんですよ。
js> var a = [, "Hello", , "World", ,] js> a.length 5 js> a[0] js> typeof a[0] undefined js> a[1] Hello js> typeof a[2] undefined js>
アハハハハ、やっぱりJavaScriptって異常にゆるいよね。JSON構文はそこまでゆるくないってことです。
ちなみに、JavaScriptのオブジェクトリテラル構文は、配列ほどにゆるくはないのですが、最後のコンマは許されます(けっこう便利)。{"x": 12, "y": -3, "color": "red",} はJavaScriptではOK。JSONではダメです。
文字列を囲むには二重引用符だけ
JavaScriptでは、"hello" も 'hello' も許されますが、JSONでは二重引用符「"」だけが使用可能です。
文字列内のエスケープが微妙に違う
文字列リテラル内では、バックスラッシュによるエスケープが使えます。が、JavaScriptとJSONでは微妙に違います。JavaScriptとJSONで共通なのは次の8種です。
- \b 後退
- \f 改ページ
- \n 改行
- \r 復帰
- \t タブ
- \" 二重引用符
- \\ バックスラッシュ (\)
- \uXXXX 4桁の16進数 XXXX で指定されたUnicode文字
JavaScriptにある \v(垂直タブ)と \'(単一引用符)は使えません。どうせ \v なんて使わんし、文字列を囲むのは二重引用符だけなので \' も不要です。
3桁の8進数、2桁の16進数による文字番号指定も使えません。JSONでは、\u にユニコード文字番号を使え、ってことですね。
さて、JavaScriptにはないのにJSONにはあるエスケープ表現は:
- \/ スラッシュ(ソリダス)
です。別にスラッシュを生で入れてもかまわないようなので、僕には意味不明です。なんだろ? これ。
仕様にないエスケープは構文エラー
バックスラッシュに続けた文字が、仕様で決められたエスケープ表現でなくても、JavaScript処理系は文句を言いません。
js> print("\h\e\l\l\o"); hello js>
厳密なJSONパーザーでは構文エラーになるでしょう。JavaScripでも将来的には構文エラーになるようです。(http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Guide:Literals より)
表 2.1 に載っていない[檜山注:仕様外の]文字の直前にバックスラッシュを付けても無視されますが、この使用法は廃止予定であり、使用を避けるべきです。
undefinedもNaNもありません
undefined、NaNは、JavaScriptリテラルではなくて、あらかじめ値が設定された大域変数です。したがって、JSONではそんなものありません。
undefinedやNaNを使いたい状況では、なにか代替表現を考える必要があります。null, "", 0, -1, false, "undefined", "NaN" などを使うことになるでしょう。
ラッパーオブジェクトは使わないのが吉
var flag = new Boolean(true); としてセットしたfalgの値は、プリミティブなブール値ではなくてオブジェクトになります。
js> var flag = new Boolean(true); js> typeof flag object js>
JSONでオブジェクトと言えば、値・名前ペアの順序なし集合です。厳密に言えば、ブール値、数値、文字列などのラッパーオブジェクトをそのままJSONにエンコーディングすることは不可能です。実際上は、valueOfメソッドで取り出した値をエンコーディングすれば済むし、それが便利そうですが、ラッパーオブジェクトとしての機能・特徴は失われます。
型システムとtypeofに関する注意
JSONの型システムは単純できれいにまとまっています。
- プリミティブな型: null, boolean, number, string
- 構造的(複合的)な型: array, object
データ表現の最初の一文字を見るだけで、型を判断できます。
- n : null
- tまたはf : boolean
- 数字または- : number
- " : string
- [ : array
- { : object
JSONにフィットしたtypeofがあるなら、それは null, boolean, number, string, array, object のいずれかを返すべきでしょう。しかし、JavaScriptのtypeofはちょっとゆがんでいます。
- typeof null は object
- typeof 配列 は object (arrayを返す実装も存在するようですが)
最後に
JavaScriptでは、jsonDataにJSON形式で記述されたテキストデータが入っている場合、eval('(' + jsonData + ')') とすればとりあえずデコードができます。この安直な(そして危険な)方法を使っていると、jsonDataがJSON仕様に違反していても見逃しちゃいます。でも、そんなにゆるくてエンカゲンなパーザーばかりではないので、違法構文は事故につながりかねません。
やっぱり、本来のJSON仕様にちゃんと従った構文を使いたいものです。
*1:僕もこれは見逃していたなー。ゼロバディング、便利そうだけどダーメッ>関係者
- 523 http://b.hatena.ne.jp/hotentry
- 463 http://reader.livedoor.com/reader/
- 434 http://b.hatena.ne.jp/
- 274 http://d.hatena.ne.jp/
- 228 http://popurls.com/
- 223 http://www.hatena.ne.jp/
- 151 http://www.google.com/reader/view/
- 138 http://b.hatena.ne.jp/add?mode=confirm&title=%u3082%u3046%u4E00%u5EA6%u3001%u3061%u3083%u3093%u3068JSON%u5165%u9580 - %u6A9C%u5C71%u6B63%u5E78%u306E%u30AD%u30DE%u30A4%u30E9%u98FC%u80B2%u8A18&url=http://d.hatena.ne.jp/m-hiyama/20080728/1217205390
- 82 http://b.hatena.ne.jp/entrylist?sort=hot
- 73 http://b.hatena.ne.jp/hotentry?