このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

[参照用 記事]

もう一度、ちゃんとJSON入門

僕自身も僕の周辺もJSONをよく使います。でも、細かい点でけっこうミスをやらかしています(苦笑)。このエントリーで、JSONを使う上で注意すべきこと/間違いやすい点をすべて列挙します。

内容 兼チェックリスト:

  1. 仕様原典さえ読めば完璧(のはずだが)
  2. 数値の前にゼロを付けてはいけない
  3. 16進数表記も禁止だよ
  4. 数値の前にプラスを付けてはいけない
  5. 小数点からはじまる数値はダメ
  6. 用語法が違うよ:プロパティとメンバー
  7. メンバー名には常に文字列を使う
  8. 空文字列""もメンバー名に使える
  9. 配列要素はキッチリと並べよう
  10. 文字列を囲むには二重引用符だけ
  11. 文字列内のエスケープが微妙に違う
  12. 仕様にないエスケープは構文エラー
  13. undefinedもNaNもありません
  14. ラッパーオブジェクトは使わないのが吉
  15. 型システムとtypeofに関する注意
  16. 最後に

仕様原典さえ読めば完璧(のはずだが)

JSONは、小さくて簡単な仕様です。次のWebページ(印刷して2ページくらい)にすべてが書いてあります。

これにちゃんと目を通せば、間違いなんて起きないはずなんですがねー。

でも実際には勘違い/間違いもありえるので、当エントリーで確認してくださいな。

数値の前にゼロを付けてはいけない

百二十三を表すのに、0123と書いてはいけません。JSON構文では、先頭の余分なゼロは許されていません。先頭ゼロを使うのは、0, 0.123, 0.1e12 のようなときだけです。

JavaScriptでは、0123と書いてもかまいません。が、これは百二十三じゃないですよ。やってみましょう。(実行例はRhino)


js> print(0123)
83
js>

先頭に0を付けると8進数になります。この記法はCの時代(もっと前?)からの伝統です。


#include

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ではダメです。 ([追記]IEのJavaScriptでは、最後のコンマは許されないようです。[/追記])

文字列を囲むには二重引用符だけ

JavaScriptでは、"hello" も 'hello' も許されますが、JSONでは二重引用符「"」だけが使用可能です。

文字列内のエスケープが微妙に違う

文字列リテラル内では、バックスラッシュによるエスケープが使えます。が、JavaScriptとJSONでは微妙に違います。JavaScriptとJSONで共通なのは次の8種です。

  1. \b 後退
  2. \f 改ページ
  3. \n 改行
  4. \r 復帰
  5. \t タブ
  6. \" 二重引用符
  7. \\ バックスラッシュ (\)
  8. \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

データ表現の最初の一文字を見るだけで、型を判断できます。

  1. n : null
  2. tまたはf : boolean
  3. 数字または- : number
  4. " : string
  5. [ : array
  6. { : 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:僕もこれは見逃していたなー。ゼロバディング、便利そうだけどダーメッ>関係者