## 41.創建和解析 JSON(`JSON`)
> 原文: [http://exploringjs.com/impatient-js/ch_json.html](http://exploringjs.com/impatient-js/ch_json.html)
JSON(“JavaScript Object Notation”)是一種使用文本對數據進行編碼的存儲格式。它的語法是 JavaScript 表達式的一個子集。例如,考慮以下數據,以文本形式存儲在文件`jane.json`中:
```json
{
"first": "Jane",
"last": "Porter",
"married": true,
"born": 1890,
"friends": [ "Tarzan", "Cheeta" ]
}
```
JavaScript 具有全局命名空間對象`JSON`提供了用于創建和解析 JSON 的方法。
### 41.1。 JSON 的發現和標準化
道格拉斯克羅克福德于 2001 年在 [`json.org`](http://json.org/) 上發表了 JSON 規范。他解釋說:
> 我發現了 JSON。我并沒有聲稱發明了 JSON,因為它已經存在于自然界中。我做的是我發現它,我命名它,我描述了它是如何有用的。我并不是第一個發現它的人;我知道還有其他人在我做之前至少一年才發現它。我發現的最早的事情是,早在 1996 年,Netscape 就有人使用 JavaScript 數組字面值進行數據通信,至少在我偶然發現這個想法之前的五年。
后來,JSON 被標準化為 [ECMA-404](https://www.ecma-international.org/publications/standards/Ecma-404.htm) :
* 第 1 版:2013 年 10 月
* 第 2 版??:2017 年 12 月
#### 41.1.1。 JSON 的語法被凍結了
引用 ECMA-404 標準:
> 因為它非常簡單,所以不期望 JSON 語法會發生變化。這使得 JSON 作為一種基本符號,具有極大的穩定性。
因此,JSON 永遠不會得到諸如可選的尾隨逗號,注釋或不帶引號的密鑰之類的改進 - 無論它們是否被認為是合乎需要的。但是,這仍然留下了創建編譯為普通 JSON 的 JSON 超集的空間。
### 41.2。 JSON 語法
JSON 由以下 JavaScript 部分組成:
* 復合:
* 對象字面值:
* 鍵是雙引號字符串。
* 值是 JSON 值。
* 不允許使用尾隨逗號。
* 數組字面值:
* 元素是 JSON 值。
* 不允許使用漏洞或尾隨逗號。
* 原子:
* `null`(但不是`undefined`)
* 布爾
* 數字(不包括`NaN`,`+Infinity`,`-Infinity`)
* 字符串(必須雙引號)
因此,您不能(直接)在 JSON 中表示循環結構。
### 41.3。使用`JSON` API
全局命名空間對象`JSON`包含用于處理 JSON 數據的方法。
#### 41.3.1。 `JSON.stringify(value, replacer?, space?)`
`.stringify()`將 JavaScript `value`轉換為 JSON 字符串。
##### 41.3.1.1。結果:單行文本
如果只提供第一個參數,`.stringify()`將返回單行文本:
```js
assert.equal(
JSON.stringify({foo: ['a', 'b']}),
`{"foo":["a","b"]}`);
```
##### 41.3.1.2。結果:一條縮進線的樹
如果你為`space`提供一個非負整數(我們在這里忽略`replacer`,后面的 [](ch_json.html#json-replacers-revivers) 解釋了),那么`.stringify()`會返回一個或多個行和每個`space`空格的縮進嵌套水平:
```js
assert.equal(
JSON.stringify({foo: ['a', 'b']}, null, 2),
`{
"foo": [
"a",
"b"
]
}`);
```
##### 41.3.1.3。有關 JavaScript 值如何字符串化的詳細信息
支持的原始值按預期進行字符串化:
```js
> JSON.stringify('abc')
'"abc"'
> JSON.stringify(123)
'123'
> JSON.stringify(null)
'null'
```
非有限數字(包括`NaN`)被字符串化為`'null'`:
```js
> JSON.stringify(NaN)
'null'
> JSON.stringify(Infinity)
'null'
```
不支持的原始值被字符串化為`undefined`:
```js
> JSON.stringify(undefined)
undefined
> JSON.stringify(Symbol())
undefined
```
函數字符串化為`undefined`:
```js
> JSON.stringify(() => {})
undefined
```
在數組中,將字符串化為`undefined`的元素被字符串化為`'null'`:
```js
> JSON.stringify([undefined, 123, () => {}])
'[null,123,null]'
```
在一個對象(既不是數組也不是函數)中,將跳過其值將被字符串化為`undefined`的屬性:
```js
> JSON.stringify({a: Symbol(), b: true})
'{"b":true}'
```
如果一個對象(可能是一個數組或一個函數)有一個方法`.toJSON()`,那么該方法的結果將被字符串化,而不是該對象。例如,日期有一個返回字符串的方法`.toJSON()`。
```js
> JSON.stringify({toJSON() {return true}})
'true'
> JSON.stringify(new Date(2999, 11, 31))
'"2999-12-30T23:00:00.000Z"'
```
有關字符串化的更多詳細信息,請參閱 [ECMAScript 規范](https://tc39.github.io/ecma262/#sec-serializejsonproperty)。
#### 41.3.2。 `JSON.parse(text, reviver?)`
`.parse()`將 JSON `text`轉換為 JavaScript 值:
```js
> JSON.parse('{"foo":["a","b"]}')
{ foo: [ 'a', 'b' ] }
```
參數`reviver`稍后解釋 [](ch_json.html#json-replacers-revivers) 。
#### 41.3.3。示例:轉換為 JSON 和從 JSON 轉換
以下類演示了一種實現 JSON 轉換的技術:
```js
class Point {
static fromJson(jsonObj) {
return new Point(jsonObj.x, jsonObj.y);
}
constructor(x, y) {
this.coord = [x, y];
}
toJSON() {
const [x, y] = this.coord;
return {x, y};
}
}
assert.equal(
JSON.stringify(new Point(3, 5)),
'{"x":3,"y":5}');
assert.deepEqual(
Point.fromJson(JSON.parse('{"x":3,"y":5}')),
new Point(3, 5));
```
前面提到的 [](ch_json.html#stringification-details) 方法`.toJSON()`用于字符串化`Point`的實例。
 **練習:將對象轉換為 JSON**
`exercises/json/to_from_json_test.js`
### 41.4。配置字符串化或解析的內容(高級)
字符串化或解析的內容可以配置如下:
* `.stringify()`具有可選參數`replacer`,其中包含:
* 具有屬性名稱的 Array。在對對象(可能是嵌套的)進行字符串化時,只會考慮這些屬性,所有其他屬性都將被忽略。
* 一個 _ 值 visitor_ - 一個可以在字符串化之前轉換 JavaScript 值的函數。
* `.parse()`具有可選參數`reviver` - 一個值訪問者,可以在返回之前轉換已解析的 JSON 數據。
#### 41.4.1。 `.stringfy()`:指定對象應具有的唯一屬性
如果`.stringify()`的第二個參數是一個數組,那么結果中只包含在數組中提到其名稱的對象屬性:
```js
const obj = {
a: 1,
b: {
c: 2,
d: 3,
}
};
assert.equal(
JSON.stringify(obj, ['b', 'c']),
'{"b":{"c":2}}');
```
#### 41.4.2。 `.stringify()`和`.parse()`:重視訪客
我稱之為 _ 值訪問者 _ 是一個轉換 JavaScript 值(復合或原子)的函數:
* `JSON.stringify()`在其接收到的 JavaScript 值之前調用其參數`replacer`中的值 visitor。
* 解析 JSON 數據后,`JSON.parse()`在其參數`reviver`中調用值 visitor。
JavaScript 值的轉換如下:值為原子值或復合值,并包含更多值(嵌套在數組和對象中)。原子值或嵌套值一次一個地饋送到值 vistor。根據訪問者返回的內容,將刪除,更改或保留當前值。
值訪問者具有以下類型簽名:
```js
type ValueVisitor = (this: object, key: string, value: any) => any;
```
參數是:
* `value`:當前值。
* `this`:當前值的父級。根值`r`的父級是`{'': r}`。
* `key`:其父級內當前值的鍵或索引。空字符串用于根值。
值訪問者可以返回:
* `value`:表示不會有任何變化。
* 不同的值`x`:導致`value`被`x`取代。
* `undefined`:導致`value`被移除。
#### 41.4.3。示例:訪問值
以下代碼演示了值訪問者查看值的順序。
```js
const log = [];
function valueVisitor(key, value) {
log.push({key, value, this: this});
return value; // no change
}
const root = {
a: 1,
b: {
c: 2,
d: 3,
}
};
JSON.stringify(root, valueVisitor);
assert.deepEqual(log, [
{ key: '', value: root, this: { '': root } },
{ key: 'a', value: 1, this: root },
{ key: 'b', value: root.b, this: root },
{ key: 'c', value: 2, this: root.b },
{ key: 'd', value: 3, this: root.b },
]);
```
如您所見,`.stringify()`從上到下訪問值(根首先,最后留下)。相反,`.parse()`訪問自下而上的值(先離開,最后一根)。
#### 41.4.4。示例:對不支持的值進行字符串化
`.stringify()`對正則表達式對象沒有特殊支持 - 它將它們字符串化為就像它們是普通對象一樣:
```js
const obj = {
name: 'abc',
regex: /abc/ui,
};
assert.equal(
JSON.stringify(obj),
'{"name":"abc","regex":{}}');
```
我們可以通過替換器解決這個問題:
```js
function replacer(key, value) {
if (value instanceof RegExp) {
return {
__type__: 'RegExp',
source: value.source,
flags: value.flags,
};
} else {
return value; // no change
}
}
assert.equal(
JSON.stringify(obj, replacer, 2),
`{
"name": "abc",
"regex": {
"__type__": "RegExp",
"source": "abc",
"flags": "iu"
}
}`);
```
#### 41.4.5。示例:解析不支持的值
要`.parse()`上一節的結果,我們需要一個復活者:
```js
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"
}
}`;
assert.deepEqual(
JSON.parse(str, reviver),
{
name: 'abc',
regex: /abc/ui,
});
```
### 41.5。常問問題
#### 41.5.1。為什么 JSON 不支持評論?
道格拉斯·克羅克福德在[中解釋了為什么從 2012 年 5 月 1 日開始發布一條 Google+信息](https://plus.google.com/+DouglasCrockfordEsq/posts/RK8qyGVaGSr):
> 我從 JSON 中刪除了注釋,因為我看到有人使用它們來保存解析指令,這種做法會破壞互操作性。我知道缺乏評論會讓一些人感到悲傷,但事實并非如此。
>
> 假設您使用 JSON 來保留您想要注釋的配置文件。繼續,插入您喜歡的所有評論。然后將它傳遞給 JSMin [JavaScript 的一個 minifier],然后再將它傳遞給你的 JSON 解析器。
### 41.6。快速參考:JSON
_ 值訪客簽名 _:
```js
type ValueVisitor = (this: object, key: string, value: any) => any;
```
`JSON`:
* `.stringify(value: any, replacer?: ValueVisitor, space?: string | number): string` <sup>[ES5]</sup>
將`value`轉換為 JSON 字符串。本章前面解釋了參數`replacer` [。參數`space`的工作原理如下:](ch_json.html#json-value-visitors)
* 如果省略`space`,`.stringify()`將返回單行文本。
```js
assert.equal(
JSON.stringify({foo: 1, bar: [2, 3]}),
'{"foo":1,"bar":[2,3]}');
```
* 如果`space`是一個數字,`.stringify()`返回一行或多行,并按每個嵌套級別的`space`空格縮進。
```js
assert.equal(
JSON.stringify({foo: 1, bar: [2, 3]}, null, 2),
`{
"foo": 1,
"bar": [
2,
3
]
}`);
```
* 如果`space`是一個字符串,則用于縮進。
```js
assert.equal(
JSON.stringify({foo: 1, bar: [2, 3]}, null, '>>'),
`{
>>"foo": 1,
>>"bar": [
>>>>2,
>>>>3
>>]
}`);
```
* `.stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string` <sup>[ES5]</sup>
如果`replacer`是一個數組,則結果只包括其名稱在數組中提及的對象屬性。
```js
> JSON.stringify({foo: 1, bar: 2}, ['foo'])
'{"foo":1}'
```
* `.parse(text: string, reviver?: ValueVisitor): any` <sup>[ES5]</sup>
解析`text`中的 JSON 并返回 JavaScript 值。本章前面解釋了參數`reviver` [。](ch_json.html#json-value-visitors)
```js
assert.deepEqual(
JSON.parse('{"a":true,"b":[1,2]}'),
{ a: true, b: [1,2] }
);
```
#### 41.6.1。來源
* [TypeScript 的內置打字](https://github.com/Microsoft/TypeScript/blob/master/lib/)
- I.背景
- 1.關于本書(ES2019 版)
- 2.常見問題:本書
- 3. JavaScript 的歷史和演變
- 4.常見問題:JavaScript
- II.第一步
- 5.概覽
- 6.語法
- 7.在控制臺上打印信息(console.*)
- 8.斷言 API
- 9.測驗和練習入門
- III.變量和值
- 10.變量和賦值
- 11.值
- 12.運算符
- IV.原始值
- 13.非值undefined和null
- 14.布爾值
- 15.數字
- 16. Math
- 17. Unicode - 簡要介紹(高級)
- 18.字符串
- 19.使用模板字面值和標記模板
- 20.符號
- V.控制流和數據流
- 21.控制流語句
- 22.異常處理
- 23.可調用值
- VI.模塊化
- 24.模塊
- 25.單個對象
- 26.原型鏈和類
- 七.集合
- 27.同步迭代
- 28.數組(Array)
- 29.類型化數組:處理二進制數據(高級)
- 30.映射(Map)
- 31. WeakMaps(WeakMap)
- 32.集(Set)
- 33. WeakSets(WeakSet)
- 34.解構
- 35.同步生成器(高級)
- 八.異步
- 36. JavaScript 中的異步編程
- 37.異步編程的 Promise
- 38.異步函數
- IX.更多標準庫
- 39.正則表達式(RegExp)
- 40.日期(Date)
- 41.創建和解析 JSON(JSON)
- 42.其余章節在哪里?