第22章 JSON
目次
書籍を購入する
(広告、ブロックしないでください。)

第22章 JSON

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式から次の構成要素を使用します。

複合型
JSONデータのオブジェクトとJSONデータの配列
原子型
文字列、数値、ブール値、およびnull

以下のルールに従います。

歴史

ダグラス・クロックフォードは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がいかにシンプルであるかが視覚的に明らかになります。

文法は次のように書き起こすことができます。

object

{ }

{ members }

members

pair

pair , members

pair
string : value
array

[ ]

[ elements ]

elements

value

value , elements

value

string

number

object

array

true

false

null

string

""

" chars "

chars

char

char chars

char

"-", "\", 制御文字以外の任意のUnicode文字

\" \\ \/ \b \f \n \r \t

\u 4桁の16進数

number

int

int frac

int exp

int frac exp

int

digit

digit1-9 digits

- digit

- digit1-9 digits

frac
. digits
exp
e digits
digits

digit

digit digits

e

e e+ e-

E E+ E-

グローバル変数JSONは、JSONデータを含む文字列を作成および解析する関数の名前空間として機能します。

JSON.stringify(value, replacer?, space?)

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()で無視されるデータ

オブジェクトでは、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]'

toJSON()メソッド

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?)

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''です。
  • valuerootです。

ノードビジターには、値を返すための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()

特殊なルートノードは、プレフィックス反復(親が子より前)で最初に来ます。最初に訪問されるノードは常に擬似ルートです。stringify()によって返される文字列は、各呼び出し後に最後に表示される行です。

> 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"'

JSON.parse()

リーフはポストフィックス反復(子が親より前)で最初に来ます。最後に訪問されるノードは常に擬似ルートです。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'

次:23. 標準的なグローバル変数