JSON
) の作成とパースJSON
APIの使用JSON.stringify(data, replacer?, space?)
JSON.parse(text, reviver?)
.stringify()
: オブジェクトのどのプロパティを文字列化するかを指定する.stringify()
と .parse()
: 値ビジターJSON(「JavaScript Object Notation」)は、テキストを使用してデータをエンコードするストレージ形式です。その構文は、JavaScript式のサブセットです。例として、ファイル`jane.json`に保存されている次のテキストを考えてみましょう。
{"first": "Jane",
"last": "Porter",
"married": true,
"born": 1890,
"friends": [ "Tarzan", "Cheeta" ]
}
JavaScriptには、JSONの作成とパースのためのメソッドを提供するグローバル名前空間オブジェクト`JSON`があります。
JSONの仕様は、2001年にDouglas Crockfordによってjson.org
で公開されました。彼は次のように説明しています。
私はJSONを発見しました。JSONはすでに自然界に存在していたため、発明したとは主張しません。私がしたのは、それを見つけ、名前を付け、それがどのように役立つかを説明したことです。私が最初に発見した人物であるとは主張しません。少なくとも1年前には、他の誰かが発見していたことを知っています。私が発見した最も初期の事例は、Netscapeの誰かが、1996年という早い時期に、データ通信を行うためにJavaScriptの配列リテラルを使用していたことです。これは、私がそのアイデアに偶然出会う少なくとも5年前のことです。
その後、JSONはECMA-404として標準化されました。
ECMA-404規格からの引用
JSONの文法は非常にシンプルであるため、変更されることはないと予想されます。これにより、JSONは基礎となる表記法として、非常に安定したものになります。
したがって、JSONは、オプションの末尾のカンマ、コメント、引用符のないキーなど、望ましいかどうかに関係なく、改善されることはありません。ただし、プレーンJSONにコンパイルされるJSONのスーパーセットを作成する余地は残されています。
JSONは、JavaScriptの次の部分で構成されています。
null
(ただしundefined
は除く)NaN
、+Infinity
、-Infinity
は除く)結果として、JSONでは循環構造を(直接)表現することはできません。
グローバル名前空間オブジェクト`JSON`には、JSONデータを操作するためのメソッドが含まれています。
`.stringify()`は、JavaScriptの`data`をJSON文字列に変換します。このセクションでは、パラメータ`replacer`は無視しています。 §45.4「文字列化とパースのカスタマイズ」で説明しています。
最初の引数のみを指定すると、`.stringify()`は1行のテキストを返します。
.equal(
assertJSON.stringify({foo: ['a', 'b']}),
'{"foo":["a","b"]}' );
`space`に負でない整数を指定すると、`.stringify()`は1行以上の行を返し、ネストのレベルごとに`space`スペースでインデントします。
.equal(
assertJSON.stringify({foo: ['a', 'b']}, null, 2),
`{
"foo": [
"a",
"b"
]
}`);
プリミティブ値
サポートされているプリミティブ値は、期待どおりに文字列化されます。
> JSON.stringify('abc')'"abc"'
> JSON.stringify(123)'123'
> JSON.stringify(null)'null'
サポートされていない数値: `'null'`
> JSON.stringify(NaN)'null'
> JSON.stringify(Infinity)'null'
BigInt: `TypeError`
> JSON.stringify(123n)TypeError: Do not know how to serialize a BigInt
その他のサポートされていないプリミティブ値は文字列化されません。結果は`undefined`になります。
> JSON.stringify(undefined)undefined
> JSON.stringify(Symbol())undefined
オブジェクト
オブジェクトにメソッド`.toJSON()`がある場合、そのメソッドの結果が文字列化されます。
> JSON.stringify({toJSON() {return true}})'true'
日付には、文字列を返すメソッド`.toJSON()`があります。
> JSON.stringify(new Date(2999, 11, 31))'"2999-12-30T23:00:00.000Z"'
ラップされたプリミティブ値はラップ解除され、文字列化されます。
> JSON.stringify(new Boolean(true))'true'
> JSON.stringify(new Number(123))'123'
配列は配列リテラルとして文字列化されます。サポートされていない配列要素は、`null`であるかのように文字列化されます。
> JSON.stringify([undefined, 123, Symbol()])'[null,123,null]'
関数以外の他のすべてのオブジェクトは、オブジェクトリテラルとして文字列化されます。サポートされていない値を持つプロパティは省略されます。
> JSON.stringify({a: Symbol(), b: true})'{"b":true}'
関数は文字列化されません。
> JSON.stringify(() => {})undefined
`.parse()`は、JSON`text`をJavaScript値に変換します。このセクションでは、パラメータ`reviver`は無視しています。 §45.4「文字列化とパースのカスタマイズ」で説明しています。
これは、`.parse()`の使用例です。
> JSON.parse('{"foo":["a","b"]}'){ foo: [ 'a', 'b' ] }
次のクラスは、JSONからの(行A)およびJSONへの(行B)変換を実装しています。
class Point {
static fromJson(jsonObj) { // (A)
return new Point(jsonObj.x, jsonObj.y);
}
constructor(x, y) {
this.x = x;
this.y = y;
}
toJSON() { // (B)
return {x: this.x, y: this.y};
} }
JSONをポイントに変換する: 静的メソッド`Point.fromJson()`を使用してJSONをパースし、`Point`のインスタンスを作成します。
.deepEqual(
assertPoint.fromJson(JSON.parse('{"x":3,"y":5}')),
new Point(3, 5) );
ポイントをJSONに変換する: `JSON.stringify()`は、内部で前述のメソッド`.toJSON()`を呼び出します。
.equal(
assertJSON.stringify(new Point(3, 5)),
'{"x":3,"y":5}' );
演習: オブジェクトをJSONに変換する、およびJSONから変換する
exercises/json/to_from_json_test.mjs
文字列化とパースは、次のようにカスタマイズできます。
JSON.stringify(data, replacer?, space?)
オプションのパラメータ`replacer`には、次のいずれかが含まれています。
JSON.parse(text, reviver?)
オプションのパラメータ`reviver`には、パースされたJSONデータを返される前に変換できる値ビジターが含まれています。
`.stringify()`の2番目のパラメータが配列の場合、そこに名前が記載されているオブジェクトプロパティのみが結果に含まれます。
const obj = {
a: 1,
b: {
c: 2,
d: 3,
};
}.equal(
assertJSON.stringify(obj, ['b', 'c']),
'{"b":{"c":2}}');
私が*値ビジター*と呼んでいるのは、JavaScriptデータを変換する関数です。
このセクションでは、JavaScriptデータは値のツリーと見なされます。データがアトミックな場合、ルートのみを持つツリーです。ツリー内のすべての値は、一度に1つずつ値ビジターに送られます。ビジターが返す内容に応じて、現在の値は省略、変更、または保持されます。
値ビジターには、次の型のシグネチャがあります。
type ValueVisitor = (key: string, value: any) => any;
パラメータは次のとおりです。
値ビジターは、次のいずれかを返すことができます。
次のコードは、値ビジターが値を認識する順序を示しています。
const log = [];
function valueVisitor(key, value) {
.push({this: this, key, value});
logreturn value; // no change
}
const root = {
a: 1,
b: {
c: 2,
d: 3,
};
}JSON.stringify(root, valueVisitor);
.deepEqual(log, [
assertthis: { '': root }, key: '', value: root },
{ this: root , key: 'a', value: 1 },
{ this: root , key: 'b', value: root.b },
{ this: root.b , key: 'c', value: 2 },
{ this: root.b , key: 'd', value: 3 },
{ ; ])
ご覧のとおり、`JSON.stringify()`のreplacerは、値をトップダウン(ルートが最初、リーフが最後)で訪問します。その方向に進む理由は、JavaScriptの値をJSONの値に変換しているためです。また、単一のJavaScriptオブジェクトは、JSON互換の値のツリーに展開される場合があります。
対照的に、`JSON.parse()`のreviverは、値をボトムアップ(リーフが最初、ルートが最後)で訪問します。その方向に進む理由は、JSON値をJavaScript値に組み立てているためです。したがって、全体を変換する前に、部品を変換する必要があります。
`JSON.stringify()`は、正規表現オブジェクトを特別にサポートしていません。プレーンオブジェクトであるかのように文字列化します。
const obj = {
name: 'abc',
regex: /abc/ui,
;
}.equal(
assertJSON.stringify(obj),
'{"name":"abc","regex":{}}');
これは、replacerを使用して修正できます。
function replacer(key, value) {
if (value instanceof RegExp) {
return {
__type__: 'RegExp',
source: value.source,
flags: value.flags,
;
}else {
} return value; // no change
}
}.equal(
assertJSON.stringify(obj, replacer, 2),
`{
"name": "abc",
"regex": {
"__type__": "RegExp",
"source": "abc",
"flags": "iu"
}
}`);
前のセクションの結果を`JSON.parse()`するには、reviverが必要です。
function reviver(key, value) {
// Very simple check
if (value && value.__type__ === 'RegExp') {
return new RegExp(value.source, value.flags);
else {
} return value;
}
}const str = `{
"name": "abc",
"regex": {
"__type__": "RegExp",
"source": "abc",
"flags": "iu"
}
}`;
.deepEqual(
assertJSON.parse(str, reviver),
{name: 'abc',
regex: /abc/ui,
; })
Douglas Crockfordは、2012年5月1日のGoogle+投稿で、その理由を説明しています。
JSONからコメントを削除したのは、人々がコメントを使用してパースディレクティブを保持しているのを見たからです。これは、相互運用性を破壊してしまう慣行です。コメントがないと悲しくなる人がいることは知っていますが、そうあるべきではありません。
JSONを使用して、注釈を付けたい構成ファイルを保持しているとします。好きなだけコメントを挿入してください。次に、JSONパーサーに渡す前に、JSMin [JavaScriptのミニファイアー]を介してパイプします。