複雑なオブジェクトがあり、それを文字列に変換してネットワーク経由で送信したり、単にログ記録のために出力したいとします。
当然のことながら、そのような文字列にはすべての重要なプロパティが含まれている必要があります。
次のように変換を実装できます。
ユーザー = { にします 名前:「ジョン」、 年齢:30歳、 toString() { return `{名前: "${this.name}"、年齢: ${this.age}}`; } }; アラート(ユーザー); // {名前: "ジョン"、年齢: 30}
…しかし、開発の過程で、新しいプロパティが追加され、古いプロパティは名前変更され、削除されます。このようなtoString
毎回更新するのは面倒になる可能性があります。その中のプロパティをループしてみることもできますが、オブジェクトが複雑で、プロパティにネストされたオブジェクトがある場合はどうなるでしょうか?それらの変換も実装する必要があります。
幸いなことに、これらすべてを処理するコードを記述する必要はありません。その課題はすでに解決されています。
JSON (JavaScript Object Notation) は、値とオブジェクトを表す一般的な形式です。これは RFC 4627 標準で説明されています。当初は JavaScript 用に作成されましたが、他の多くの言語にも同様にそれを処理するライブラリがあります。 したがって、クライアントが JavaScript を使用し、サーバーが Ruby/PHP/Java/何でもで書かれている場合、データ交換に JSON を使用するのは簡単です。
JavaScript は次のメソッドを提供します。
JSON.stringify
オブジェクトを JSON に変換します。
JSON.parse
使用して JSON をオブジェクトに変換します。
たとえば、ここでは学生をJSON.stringify
ます。
学生 = { にしてみましょう 名前:「ジョン」、 年齢:30歳、 isAdmin: false、 コース: ['html'、'css'、'js']、 配偶者: null }; let json = JSON.stringify(student); アラート(jsonの種類); // 文字列ができました! アラート(json); /* JSON エンコードされたオブジェクト: { "名前": "ジョン", 「年齢」:30歳、 "isAdmin": false、 "コース": ["html"、"css"、"js"]、 「配偶者」: null } */
メソッドJSON.stringify(student)
はオブジェクトを取得し、それを文字列に変換します。
結果のjson
文字列は、 JSON エンコードされたオブジェクト、シリアル化されたオブジェクト、文字列化されたオブジェクト、またはマーシャリングされたオブジェクトと呼ばれます。ネットワーク経由で送信したり、プレーンなデータ ストアに保存したりする準備ができています。
JSON エンコードされたオブジェクトには、オブジェクト リテラルといくつかの重要な違いがあることに注意してください。
文字列には二重引用符が使用されます。 JSON には一重引用符やバッククォートは使用できません。したがって、 'John'
は"John"
になります。
オブジェクトのプロパティ名も二重引用符で囲まれます。それは義務です。したがって、 age:30
は"age":30
になります。
JSON.stringify
プリミティブにも適用できます。
JSON は次のデータ型をサポートします。
オブジェクト{ ... }
配列[ ... ]
プリミティブ:
文字列、
数字、
ブール値true/false
、
null
。
例えば:
// JSON 内の数値は単なる数値です alert( JSON.stringify(1) ) // 1 // JSON 内の文字列は文字列のままですが、二重引用符で囲まれています alert( JSON.stringify('test') ) // "テスト" アラート( JSON.stringify(true) ); // 真実 アラート( JSON.stringify([1, 2, 3]) ); // [1,2,3]
JSON はデータのみで言語に依存しない仕様であるため、一部の JavaScript 固有のオブジェクト プロパティはJSON.stringify
によってスキップされます。
つまり:
関数のプロパティ (メソッド)。
シンボリックキーとシンボリック値。
undefined
格納するプロパティ。
ユーザー = { にします SayHi() { // 無視される アラート("こんにちは"); }、 [Symbol("id")]: 123, // 無視される 何か: 未定義 // 無視される }; アラート( JSON.stringify(user) ); // {} (空のオブジェクト)
通常はそれで問題ありません。それが望ましくない場合は、プロセスをカスタマイズする方法をすぐに説明します。
素晴らしい点は、ネストされたオブジェクトがサポートされ、自動的に変換されることです。
例えば:
ミートアップをさせてください = { タイトル: 「カンファレンス」、 部屋: { 番号: 23、 参加者: ["ジョン"、"アン"] } }; アラート( JSON.stringify(meetup) ); /* 構造全体が文字列化されます。 { "タイトル":"カンファレンス", "部屋":{"番号":23,"参加者":["ジョン","アン"]}, } */
重要な制限: 循環参照があってはなりません。
例えば:
部屋を許可します = { 番号: 23 }; ミートアップをさせてください = { タイトル: 「カンファレンス」、 参加者: ["ジョン"、"アン"] }; ミートアップ場所 = 部屋; // ミートアップのリファレンスルーム room.occupiedBy = ミーティング; // ルームリファレンスのミートアップ JSON.stringify(ミートアップ); // エラー: 循環構造を JSON に変換しています
ここでは、循環参照のために変換が失敗します。 room.occupiedBy
はmeetup
を参照し、 meetup.place
room
参照します。
JSON.stringify
の完全な構文は次のとおりです。
let json = JSON.stringify(value[, replacer, space])
価値
エンコードする値。
交換者
エンコードするプロパティの配列、またはマッピング関数function(key, value)
。
空間
フォーマットに使用するスペースの量
ほとんどの場合、 JSON.stringify
は最初の引数とともにのみ使用されます。ただし、循環参照を除外するなど、置換プロセスを微調整する必要がある場合は、 JSON.stringify
の 2 番目の引数を使用できます。
プロパティの配列を渡すと、これらのプロパティのみがエンコードされます。
例えば:
部屋を許可します = { 番号: 23 }; ミートアップをさせてください = { タイトル: 「カンファレンス」、 参加者: [{名前: "ジョン"}、{名前: "アリス"}]、 place: room // ミートアップ参考部屋 }; room.occupiedBy = ミーティング; // ルームリファレンスのミートアップ alert( JSON.stringify(meetup, ['タイトル', '参加者']) ); // {"タイトル":"会議","参加者":[{},{}]}
ここではおそらく厳しすぎるでしょう。プロパティ リストはオブジェクト構造全体に適用されます。したがって、 name
がリストにないため、 participants
のオブジェクトは空になります。
循環参照を引き起こすroom.occupiedBy
を除くすべてのプロパティをリストに含めてみましょう。
部屋を許可します = { 番号: 23 }; ミートアップをさせてください = { タイトル: 「カンファレンス」、 参加者: [{名前: "ジョン"}、{名前: "アリス"}]、 place: room // ミートアップ参考部屋 }; room.occupiedBy = ミーティング; // ルームリファレンスのミートアップ alert( JSON.stringify(meetup, ['タイトル', '参加者', '場所', '名前', '番号']) ); /* { "タイトル":"カンファレンス", "参加者":[{"名前":"ジョン"},{"名前":"アリス"}], "場所":{"番号":23} } */
これで、 occupiedBy
除くすべてがシリアル化されました。ただし、プロパティのリストは非常に長いです。
幸いなことに、配列の代わりに関数をreplacer
として使用できます。
この関数はすべての(key, value)
ペアに対して呼び出され、元の値の代わりに使用される「置換された」値を返す必要があります。値をスキップする場合はundefined
。
私たちのケースでは、 occupiedBy
除くすべてのvalue
を「そのまま」返すことができます。 occupiedBy
無視するには、以下のコードはundefined
を返します。
部屋を許可します = { 番号: 23 }; ミートアップをさせてください = { タイトル: 「カンファレンス」、 参加者: [{名前: "ジョン"}、{名前: "アリス"}]、 place: room // ミートアップ参考部屋 }; room.occupiedBy = ミーティング; // ルームリファレンスのミートアップ alert( JSON.stringify(meetup, function replacer(key, value) { アラート(`${キー}: ${値}`); return (key == 'occupiedBy') ?未定義: 値; })); /* 置換に使用されるキー:値のペア: : [オブジェクト オブジェクト] タイトル: カンファレンス 参加者: [オブジェクト オブジェクト]、[オブジェクト オブジェクト] 0: [オブジェクト オブジェクト] 名前:ジョン 1: [オブジェクト オブジェクト] 名前:アリス 場所: [オブジェクト オブジェクト] 番号: 23 占有者: [オブジェクト オブジェクト] */
replacer
関数は、ネストされたオブジェクトや配列項目を含むすべてのキーと値のペアを取得することに注意してください。再帰的に適用されます。 this
内部replacer
の値は、現在のプロパティを含むオブジェクトです。
最初の電話は特別です。これは特別な「ラッパー オブジェクト」: {"": meetup}
を使用して作成されます。つまり、最初の(key, value)
ペアには空のキーがあり、値が全体としてターゲット オブジェクトになります。上の例では最初の行が":[object Object]"
になっているのはそのためです。
考え方は、 replacer
にできる限り多くの機能を提供することです。replacer には、必要に応じてオブジェクト全体を分析して置換/スキップする機会もあります。
JSON.stringify(value, replacer, space)
の 3 番目の引数は、整形に使用するスペースの数です。
以前は、文字列化されたすべてのオブジェクトにはインデントや余分なスペースがありませんでした。ネットワーク経由でオブジェクトを送信したい場合はそれで問題ありません。 space
引数は、優れた出力のためにのみ使用されます。
ここで、 space = 2
オブジェクト内に 2 つのスペースをインデントして、ネストされたオブジェクトを複数行に表示するように JavaScript に指示します。
ユーザー = { にします 名前:「ジョン」、 年齢:25歳、 役割: { isAdmin: false、 isEditor: true } }; alert(JSON.stringify(user, null, 2)); /* 2 つのスペースのインデント: { "名前": "ジョン", 「年齢」:25歳、 「役割」: { "isAdmin": false、 "isEditor": true } } */ /* JSON.stringify(user, null, 4) の場合、結果はさらにインデントされます。 { "名前": "ジョン", 「年齢」:25歳、 「役割」: { "isAdmin": false、 "isEditor": true } } */
3 番目の引数には文字列を指定することもできます。この場合、スペース数の代わりに文字列がインデントに使用されます。
space
パラメーターは、ログ記録と優れた出力の目的のみに使用されます。
文字列変換のtoString
と同様に、オブジェクトは to-JSON 変換のためのメソッドtoJSON
を提供する場合があります。 JSON.stringify
利用可能な場合は自動的に呼び出します。
例えば:
部屋を許可します = { 番号: 23 }; ミートアップをさせてください = { タイトル: 「カンファレンス」、 日付: 新しい日付(Date.UTC(2017, 0, 1)), 部屋 }; アラート( JSON.stringify(meetup) ); /* { "タイトル":"カンファレンス", "date":"2017-01-01T00:00:00.000Z", // (1) "部屋": {"番号":23} // (2) } */
ここでは、 date
(1)
が文字列になっていることがわかります。これは、すべての日付にそのような種類の文字列を返すtoJSON
メソッドが組み込まれているためです。
次に、オブジェクトroom
(2)
にカスタムtoJSON
追加しましょう。
部屋を許可します = { 番号: 23、 toJSON() { this.number を返します。 } }; ミートアップをさせてください = { タイトル: 「カンファレンス」、 部屋 }; アラート( JSON.stringify(部屋) ); // 23 アラート( JSON.stringify(meetup) ); /* { "タイトル":"カンファレンス", 「部屋」:23 } */
ご覧のとおり、 toJSON
JSON.stringify(room)
の直接呼び出しと、 room
が別のエンコードされたオブジェクトにネストされている場合の両方に使用されます。
JSON 文字列をデコードするには、JSON.parse という名前の別のメソッドが必要です。
構文:
let value = JSON.parse(str[, reviver]);
str
解析する JSON 文字列。
リバイバー
(key, value)
ペアごとに呼び出され、値を変換できるオプションの関数 (キー, 値)。
例えば:
// 文字列化された配列 数値 = "[0, 1, 2, 3]"; 数値 = JSON.parse(数値); アラート(数値[1]); // 1
または、ネストされたオブジェクトの場合:
let userData = '{ "名前": "ジョン", "年齢": 35, "isAdmin": false, "友達": [0,1,2,3] }'; let user = JSON.parse(userData); アラート( user.friends[1] ); // 1
JSON は必要に応じて複雑にすることができ、オブジェクトと配列には他のオブジェクトと配列を含めることができます。ただし、同じ JSON 形式に従う必要があります。
手書きの JSON の典型的な間違いを次に示します (デバッグ目的で JSON を作成する必要がある場合があります)。
let json = `{ name: "John", // 間違い: 引用符のないプロパティ名 "surname": 'Smith', // 間違い: 値に一重引用符が含まれています (二重引用符である必要があります) 'isAdmin': false // 間違い: キーに一重引用符が含まれています (二重引用符である必要があります) "birthday": new Date(2000, 2, 3), // 間違い: "new" は使用できません。裸の値のみです。 "friends": [0,1,2,3] // ここでは大丈夫です }`;
また、JSON はコメントをサポートしていません。 JSON にコメントを追加すると無効になります。
JSON5 という別の形式もあり、引用符で囲まれていないキーやコメントなどが許可されます。ただし、これはスタンドアロンのライブラリであり、言語の仕様には含まれていません。
通常の JSON がそれほど厳密なのは、開発者が怠けているからではなく、解析アルゴリズムを簡単かつ信頼性が高く、非常に高速に実装できるようにするためです。
サーバーから文字列化されたmeetup
オブジェクトを取得したと想像してください。
次のようになります。
// タイトル: (ミーティングのタイトル)、日付: (ミーティングの日付) let str = '{"title":"カンファレンス","date":"2017-11-30T12:00:00.000Z"}';
…そして今度は、それを逆シリアル化し、JavaScript オブジェクトに戻す必要があります。
JSON.parse
を呼び出して実行しましょう。
let str = '{"title":"カンファレンス","date":"2017-11-30T12:00:00.000Z"}'; let meetup = JSON.parse(str); アラート(meetup.date.getDate() ); // エラー!
おっと!エラーです!
meetup.date
の値は文字列であり、 Date
オブジェクトではありません。 JSON.parse
その文字列をDate
に変換する必要があることをどのようにして知ることができるのでしょうか?
JSON.parse
に reviving 関数を 2 番目の引数として渡します。この関数はすべての値を「そのまま」返しますが、 date
Date
になります。
let str = '{"title":"カンファレンス","date":"2017-11-30T12:00:00.000Z"}'; let meetup = JSON.parse(str, function(key, value) { if (key == 'date') 新しい Date(value) を返します。 戻り値; }); アラート(meetup.date.getDate() ); // 動作するようになりました。
ちなみに、これはネストされたオブジェクトでも同様に機能します。
スケジュールを設定 = `{ 「ミーティング」: [ {"タイトル":"カンファレンス","日付":"2017-11-30T12:00:00.000Z"}, {"タイトル":"誕生日","日付":"2017-04-18T12:00:00.000Z"} ] }`; スケジュール = JSON.parse(スケジュール, 関数(キー, 値) { if (key == 'date') は新しい Date(value) を返します。 戻り値; }); alert(schedule.meetups[1].date.getDate() ); // うまくいきます!
JSON は、ほとんどのプログラミング言語に対して独自の独立した標準とライブラリを持つデータ形式です。
JSON は、プレーン オブジェクト、配列、文字列、数値、ブール値、およびnull
をサポートします。
JavaScript には、JSON にシリアル化するためのメソッド JSON.stringify と、JSON から読み取るための JSON.parse メソッドが用意されています。
どちらの方法も、スマートな読み取り/書き込みのためのトランスフォーマー機能をサポートしています。
オブジェクトにtoJSON
がある場合、それはJSON.stringify
によって呼び出されます。
重要度: 5
user
JSON に変換し、それを別の変数に読み込みます。
ユーザー = { にします 名前:「ジョン・スミス」、 年齢: 35歳 };
ユーザー = { にします 名前:「ジョン・スミス」、 年齢: 35歳 }; let user2 = JSON.parse(JSON.stringify(user));
重要度: 5
循環参照の単純なケースでは、問題のあるプロパティをその名前によってシリアル化から除外できます。
ただし、名前は循環参照と通常のプロパティの両方で使用される可能性があるため、名前だけを使用できない場合があります。したがって、その値によってプロパティを確認できます。
replacer
関数を作成してすべてを文字列化しますが、 meetup
参照するプロパティを削除します。
部屋を許可します = { 番号: 23 }; ミートアップをさせてください = { タイトル: 「カンファレンス」、 占有者: [{名前: "ジョン"}、{名前: "アリス"}]、 場所:部屋 }; // 循環参照 room.occupiedBy = ミーティング; ミートアップ.セルフ = ミートアップ; alert( JSON.stringify(meetup, function replacer(key, value) { /* あなたのコード */ })); /* 結果は次のようになります: { "タイトル":"カンファレンス", "占有者":[{"名前":"ジョン"},{"名前":"アリス"}], "場所":{"番号":23} } */
部屋を許可します = { 番号: 23 }; ミートアップをさせてください = { タイトル: 「カンファレンス」、 占有者: [{名前: "ジョン"}、{名前: "アリス"}]、 場所:部屋 }; room.occupiedBy = ミーティング; ミートアップ.セルフ = ミートアップ; alert( JSON.stringify(meetup, function replacer(key, value) { return (key != "" && value == ミートアップ) ?未定義: 値; })); /* { "タイトル":"カンファレンス", "占有者":[{"名前":"ジョン"},{"名前":"アリス"}], "場所":{"番号":23} } */
ここでは、 key==""
をテストして、 value
がmeetup
であるのが通常である最初の呼び出しを除外する必要もあります。