JSON (JavaScript Object Notation) は、データ保存のためのプレーンテキスト形式です。Webサービス、設定ファイルなどにおけるデータ交換形式として非常に人気があります。ECMAScript 5には、JSON形式の文字列からJavaScriptの値への変換(パース)と、その逆(文字列化)のためのAPIがあります。
このセクションでは、JSONとは何か、そしてどのように作成されたのかを説明します。
JSONはプレーンテキストとしてデータを保存します。その文法は、JavaScript式の文法のサブセットです。例:
{
"first"
:
"Jane"
,
"last"
:
"Porter"
,
"married"
:
true
,
"born"
:
1890
,
"friends"
:
[
"Tarzan"
,
"Cheeta"
]
}
JSONは、JavaScript式から次の構成要素を使用します。
null
以下のルールに従います。
'mystr'
のような文字列リテラルは無効です。ダグラス・クロックフォードは2001年にJSONを発見しました。彼はJSONに名前を付け、仕様をhttp://json.orgに掲載しました。
私はJSONを発見しました。JSONを発明したとは主張しません。なぜなら、それはすでに自然界に存在していたからです。私が行ったのは、それを発見し、名付け、それがどのように役立つのかを説明したことです。私が最初の人物だったとは主張しません。少なくとも1年前には、それを発見した人が他にいたことを知っています。私が発見した最も古い事例は、Netscapeにいた誰かが、少なくとも私がそのアイデアに遭遇する5年前の1996年には、データ通信にJavaScript配列リテラルを使用していたことです。
当初、クロックフォードはJSONをJavaScript Markup Languageと名付けたいと考えていましたが、JSMLという頭字語はすでにJSpeech Markup Languageで使用されていました。
JSON仕様は多くの言語に翻訳されており、現在ではJSONのパースと生成をサポートする多くのプログラミング言語のライブラリが存在します。
ダグラス・クロックフォードは、表面にロゴのあるJSONの名刺を作成しました(図22-1参照)裏面には完全な文法が記載されています(図22-2参照)。これにより、JSONがいかにシンプルであるかが視覚的に明らかになります。
文法は次のように書き起こすことができます。
{
}
{
members }
pair
pair ,
members
:
value
[
]
[
elements ]
value
value ,
elements
string
number
object
array
true
false
null
""
"
chars "
char
char chars
"-", "\", 制御文字以外の任意のUnicode文字
\" \\ \/ \b \f \n \r \t
\u
4桁の16進数
int
int frac
int exp
int frac exp
digit
digit1-9 digits
-
digit
-
digit1-9 digits
.
digitsdigit
digit digits
e e+ e-
E E+ E-
グローバル変数JSON
は、JSONデータを含む文字列を作成および解析する関数の名前空間として機能します。
JSON.stringify(value, replacer?, space?)
はJavaScriptの値value
をJSON形式の文字列に変換します。2つのオプションの引数があります。
オプションのパラメータreplacer
は、文字列化する前にvalue
を変更するために使用されます。これは次のいずれかになります。
ノードビジター(ノードビジターによるデータ変換参照)は、文字列化する前に値のツリーを変換します。例:
function
replacer
(
key
,
value
)
{
if
(
typeof
value
===
'number'
)
{
value
=
2
*
value
;
}
return
value
;
}
replacerの使用
> JSON.stringify({ a: 5, b: [ 2, 8 ] }, replacer) '{"a":10,"b":[4,16]}'
キーがリストにないプロパティ(配列オブジェクト以外)をすべて隠すプロパティキーのホワイトリスト。例:
> JSON.stringify({foo: 1, bar: {foo: 1, bar: 1}}, ['bar']) '{"bar":{"bar":1}}'
ホワイトリストは配列には影響しません。
> JSON.stringify(['a', 'b'], ['0']) '["a","b"]'
オプションのパラメータspace
は、出力のフォーマットに影響を与えます。このパラメータがない場合、stringify
の結果は1行のテキストになります。
> console.log(JSON.stringify({a: 0, b: ['\n']})) {"a":0,"b":["\n"]}
これを使用すると、改行が挿入され、配列とオブジェクトによるネストの各レベルでインデントが増加します。インデントの指定方法は2通りあります。
数値にインデントのレベルを掛け、その数のスペースだけ行をインデントします。0未満の数は0として解釈され、10を超える数は10として解釈されます。
> console.log(JSON.stringify({a: 0, b: ['\n']}, null, 2)) { "a": 0, "b": [ "\n" ] }
インデントするには、指定された文字列をインデントのレベルごとに1回繰り返します。文字列の先頭10文字のみが使用されます。
> console.log(JSON.stringify({a: 0, b: ['\n']}, null, '|--')) { |--"a": 0, |--"b": [ |--|--"\n" |--] }
したがって、JSON.stringify()
の次の呼び出しは、オブジェクトを綺麗にフォーマットされたツリーとして出力します。
JSON
.
stringify
(
data
,
null
,
4
)
オブジェクトでは、JSON.stringify()
は列挙可能な独自の属性のみを考慮します(プロパティ属性とプロパティ記述子参照)。次の例は、列挙不可能な独自の属性obj.foo
が無視されることを示しています。
> var obj = Object.defineProperty({}, 'foo', { enumerable: false, value: 7 }); > Object.getOwnPropertyNames(obj) [ 'foo' ] > obj.foo 7 > JSON.stringify(obj) '{}'
JSON.stringify()
がJSONでサポートされていない値(関数やundefined
など)をどのように処理するかは、それが値に出会う場所によって異なります。サポートされていない値自体が原因で、stringify()
は文字列ではなくundefined
を返します。
> JSON.stringify(function () {}) undefined
値がサポートされていないプロパティは単純に無視されます。
> JSON.stringify({ foo: function () {} }) '{}'
配列内のサポートされていない値はnull
として文字列化されます。
> JSON.stringify([ function () {} ]) '[null]'
JSON.stringify()
がtoJSON
メソッドを持つオブジェクトに遭遇した場合、そのメソッドを使用して文字列化する値を取得します。例:
> JSON.stringify({ toJSON: function () { return 'Cool' } }) '"Cool"'
日付にはすでにtoJSON
メソッドがあり、ISO 8601日付文字列を生成します。
> JSON.stringify(new Date('2011-07-29')) '"2011-07-28T22:00:00.000Z"'
toJSON
メソッドの完全なシグネチャは次のとおりです。
function
(
key
)
key
パラメータを使用すると、コンテキストに応じて異なる方法で文字列化できます。これは常に文字列であり、親オブジェクト内でオブジェクトがどこに見つかったかを示します。
次のオブジェクトを使用してtoJSON()
を示します。
var
obj
=
{
toJSON
:
function
(
key
)
{
// Use JSON.stringify for nicer-looking output
console
.
log
(
JSON
.
stringify
(
key
));
return
0
;
}
};
JSON.stringify()
を使用すると、obj
の各出現箇所が0
に置き換えられます。toJSON()
メソッドは、obj
がプロパティキー'foo'
と配列インデックス0で見つかったことを通知されます。
> JSON.stringify({ foo: obj, bar: [ obj ]}) "foo" "0" '{"foo":0,"bar":[0]}'
組み込みのtoJSON()
メソッドは次のとおりです。
Boolean.prototype.toJSON()
Number.prototype.toJSON()
String.prototype.toJSON()
Date.prototype.toJSON()
JSON.parse(text, reviver?)
はtext
内のJSONデータを解析し、JavaScriptの値を返します。いくつかの例を以下に示します。
> JSON.parse("'String'") // illegal quotes SyntaxError: Unexpected token ILLEGAL > JSON.parse('"String"') 'String' > JSON.parse('123') 123 > JSON.parse('[1, 2, 3]') [ 1, 2, 3 ] > JSON.parse('{ "hello": 123, "world": 456 }') { hello: 123, world: 456 }
オプションのパラメータreviver
はノードビジター(ノードビジターによるデータ変換参照)であり、解析されたデータを変換するために使用できます。この例では、日付文字列を日付オブジェクトに変換しています。
function
dateReviver
(
key
,
value
)
{
if
(
typeof
value
===
'string'
)
{
var
x
=
Date
.
parse
(
value
);
if
(
!
isNaN
(
x
))
{
// valid date string?
return
new
Date
(
x
);
}
}
return
value
;
}
そしてこれがインタラクションです。
> var str = '{ "name": "John", "birth": "2011-07-28T22:00:00.000Z" }'; > JSON.parse(str, dateReviver) { name: 'John', birth: Thu, 28 Jul 2011 22:00:00 GMT }
JSON.stringify()
とJSON.parse()
の両方で関数を渡すことでJavaScriptデータを変換できます。
JSON.stringify()
を使用すると、JSONに変換する前にJavaScriptデータを変更できます。JSON.parse()
はJSONを解析し、結果のJavaScriptデータを後処理できます。JavaScriptデータは、複合ノードが配列とオブジェクトであり、リーフがプリミティブ値(ブール値、数値、文字列、null
)であるツリーです。渡す変換関数にノードビジターという名前を使用しましょう。メソッドはツリーを反復処理し、各ノードに対してビジターを呼び出します。次に、ノードを置換または削除するオプションがあります。ノードビジターは次のシグネチャを持ちます。
function
nodeVisitor
(
key
,
value
)
パラメータは次のとおりです。
this
key
key
は常に文字列です。value
ルートノードroot
には親がありません。root
が訪問されると、擬似親が作成され、パラメータは次の値を持ちます。
this
は{ '': root }
です。key
は''
です。value
はroot
です。ノードビジターには、値を返すための3つのオプションがあります。
value
をそのまま返します。変更は実行されません。undefined
を返します。ノードは削除されます。ノードビジターの例を以下に示します。渡された値をログ出力します。
function
nodeVisitor
(
key
,
value
)
{
console
.
log
([
// Use JSON.stringify for nicer-looking output
JSON
.
stringify
(
this
),
// parent
JSON
.
stringify
(
key
),
JSON
.
stringify
(
value
)
].
join
(
' # '
));
return
value
;
// don't change node
}
この関数を使用して、JSONメソッドがJavaScriptデータをどのように反復処理するかを調べましょう。
> JSON.stringify(['a','b'], nodeVisitor) {"":["a","b"]} # "" # ["a","b"] ["a","b"] # "0" # "a" ["a","b"] # "1" # "b" '["a","b"]' > JSON.stringify({a:1, b:2}, nodeVisitor) {"":{"a":1,"b":2}} # "" # {"a":1,"b":2} {"a":1,"b":2} # "a" # 1 {"a":1,"b":2} # "b" # 2 '{"a":1,"b":2}' > JSON.stringify('abc', nodeVisitor) {"":"abc"} # "" # "abc" '"abc"'
リーフはポストフィックス反復(子が親より前)で最初に来ます。最後に訪問されるノードは常に擬似ルートです。parse()
によって返されるJavaScriptの値は、各呼び出し後に最後に表示される行です。
> JSON.parse('["a","b"]', nodeVisitor) ["a","b"] # "0" # "a" ["a","b"] # "1" # "b" {"":["a","b"]} # "" # ["a","b"] [ 'a', 'b' ] > JSON.parse('{"a":1, "b":2}', nodeVisitor) {"a":1,"b":2} # "a" # 1 {"a":1,"b":2} # "b" # 2 {"":{"a":1,"b":2}} # "" # {"a":1,"b":2} { a: 1, b: 2 } > JSON.parse('"hello"', nodeVisitor) {"":"hello"} # "" # "hello" 'hello'