## 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`的实例。
![](https://img.kancloud.cn/3e/d5/3ed5755d562179ae6c199264f5e21157.svg) **练习:将对象转换为 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.其余章节在哪里?