假设我们有一个复杂的对象,并且我们希望将其转换成一个字符串,以便通过网络发送它,或者仅仅为了记录目的而输出它。
自然地,这样的字符串应该包含所有重要的属性。
我们可以像这样实现转换
let
user =
{
name:
"John"
,
age:
30
,
toString
(
)
{
return
`
{name: "
${
this
.
name}
", age:
${
this
.
age}
}
`
;
}
}
;
alert
(
user)
;
// {name: "John", age: 30}
…但在开发过程中,添加了新属性,重命名并删除了旧属性。每次更新这样的 toString
都会变成一种痛苦。我们可以尝试循环遍历其中的属性,但如果对象很复杂,并且在属性中嵌套了对象,该怎么办?我们还需要实现它们的转换。
幸运的是,没有必要编写代码来处理所有这些。这项任务已经解决了。
JSON.stringify
JSON(JavaScript 对象表示法)是一种表示值和对象的一般格式。它在 RFC 4627 标准中进行了描述。最初它是为 JavaScript 制作的,但许多其他语言也具有处理它的库。因此,当客户端使用 JavaScript 而服务器使用 Ruby/PHP/Java/Whatever 编写时,使用 JSON 进行数据交换非常容易。
JavaScript 提供了方法
JSON.stringify
将对象转换为 JSON。JSON.parse
将 JSON 转换回对象。
例如,这里我们 JSON.stringify
一个学生
let
student =
{
name:
'John'
,
age:
30
,
isAdmin:
false
,
courses:
[
'html'
,
'css'
,
'js'
]
,
spouse:
null
}
;
let
json =
JSON
.
stringify
(
student)
;
alert
(
typeof
json)
;
// we've got a string!
alert
(
json)
;
/* JSON-encoded object:
{
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"spouse": null
}
*/
方法 JSON.stringify(student)
获取对象并将其转换为字符串。
生成的 json
字符串称为JSON 编码或序列化或字符串化或编组对象。我们准备通过网络发送它或将其放入纯数据存储中。
请注意,JSON 编码对象与对象字面量有几个重要的区别
- 字符串使用双引号。JSON 中没有单引号或反引号。因此
'John'
变为"John"
。 - 对象属性名称也使用双引号。这是强制性的。因此
age:30
变为"age":30
。
JSON.stringify
也可以应用于基元。
JSON 支持以下数据类型
- 对象
{ ... }
- 数组
[ ... ]
- 基元
- 字符串,
- 数字,
- 布尔值
true/false
, null
.
例如
// a number in JSON is just a number
alert
(
JSON
.
stringify
(
1
)
)
// 1
// a string in JSON is still a string, but double-quoted
alert
(
JSON
.
stringify
(
'test'
)
)
// "test"
alert
(
JSON
.
stringify
(
true
)
)
;
// true
alert
(
JSON
.
stringify
(
[
1
,
2
,
3
]
)
)
;
// [1,2,3]
JSON 是仅限数据的语言无关规范,因此 JSON.stringify
会跳过一些特定于 JavaScript 的对象属性。
即
- 函数属性(方法)。
- 符号键和值。
- 存储
undefined
的属性。
let
user =
{
sayHi
(
)
{
// ignored
alert
(
"Hello"
)
;
}
,
[
Symbol
(
"id"
)
]
:
123
,
// ignored
something:
undefined
// ignored
}
;
alert
(
JSON
.
stringify
(
user)
)
;
// {} (empty object)
通常情况下,这很好。如果这不是我们想要的,那么我们很快就会看到如何自定义此过程。
最棒的是,它支持嵌套对象并自动转换它们。
例如
let
meetup =
{
title:
"Conference"
,
room:
{
number:
23
,
participants:
[
"john"
,
"ann"
]
}
}
;
alert
(
JSON
.
stringify
(
meetup)
)
;
/* The whole structure is stringified:
{
"title":"Conference",
"room":{"number":23,"participants":["john","ann"]},
}
*/
重要的限制:不能有循环引用。
例如
let
room =
{
number:
23
}
;
let
meetup =
{
title:
"Conference"
,
participants:
[
"john"
,
"ann"
]
}
;
meetup.
place =
room;
// meetup references room
room.
occupiedBy =
meetup;
// room references meetup
JSON
.
stringify
(
meetup)
;
// Error: Converting circular structure to JSON
此处,转换失败,因为存在循环引用:room.occupiedBy
引用 meetup
,而 meetup.place
引用 room
排除和转换:替换器
JSON.stringify
的完整语法为
let
json =
JSON
.
stringify
(
value[
,
replacer,
space]
)
- value
- 要编码的一个值。
- 替换器
- 要编码的属性数组或映射函数
function(key, value)
。 - 空格
- 用于格式化的空格量
大多数情况下,JSON.stringify
仅与第一个参数一起使用。但是,如果我们需要微调替换过程,比如过滤掉循环引用,可以使用 JSON.stringify
的第二个参数。
如果我们向其传递一个属性数组,则仅编码这些属性。
例如
let
room =
{
number:
23
}
;
let
meetup =
{
title:
"Conference"
,
participants:
[
{
name:
"John"
}
,
{
name:
"Alice"
}
]
,
place:
room // meetup references room
}
;
room.
occupiedBy =
meetup;
// room references meetup
alert
(
JSON
.
stringify
(
meetup,
[
'title'
,
'participants'
]
)
)
;
// {"title":"Conference","participants":[{},{}]}
我们在这里可能过于严格了。属性列表应用于整个对象结构。因此,participants
中的对象为空,因为 name
不在列表中。
让我们在列表中包含除 room.occupiedBy
之外的每个属性,这将导致循环引用
let
room =
{
number:
23
}
;
let
meetup =
{
title:
"Conference"
,
participants:
[
{
name:
"John"
}
,
{
name:
"Alice"
}
]
,
place:
room // meetup references room
}
;
room.
occupiedBy =
meetup;
// room references meetup
alert
(
JSON
.
stringify
(
meetup,
[
'title'
,
'participants'
,
'place'
,
'name'
,
'number'
]
)
)
;
/*
{
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
}
*/
现在除了 occupiedBy
之外的一切都已序列化。但属性列表很长。
幸运的是,我们可以使用一个函数而不是一个数组作为 replacer
。
该函数将针对每个 (key, value)
对调用,并应返回“替换”值,该值将用于替换原始值。或者,如果要跳过该值,则返回 undefined
。
在我们的例子中,我们可以为除 occupiedBy
之外的所有内容返回 value
“原样”。要忽略 occupiedBy
,以下代码返回 undefined
let
room =
{
number:
23
}
;
let
meetup =
{
title:
"Conference"
,
participants:
[
{
name:
"John"
}
,
{
name:
"Alice"
}
]
,
place:
room // meetup references room
}
;
room.
occupiedBy =
meetup;
// room references meetup
alert
(
JSON
.
stringify
(
meetup,
function
replacer
(
key,
value
)
{
alert
(
`
${
key}
:
${
value}
`
)
;
return
(
key ==
'occupiedBy'
)
?
undefined
:
value;
}
)
)
;
/* key:value pairs that come to replacer:
: [object Object]
title: Conference
participants: [object Object],[object Object]
0: [object Object]
name: John
1: [object Object]
name: Alice
place: [object Object]
number: 23
occupiedBy: [object Object]
*/
请注意,replacer
函数获取每个键/值对,包括嵌套对象和数组项。它以递归方式应用。replacer
中的 this
的值是包含当前属性的对象。
第一个调用是特殊的。它使用一个特殊的“包装对象”进行:{"": meetup}
。换句话说,第一个 (key, value)
对有一个空键,而值是整个目标对象。这就是为什么在上面的示例中第一行是 ":[object Object]"
。
这个想法是为 replacer
提供尽可能多的功能:它有机会分析和替换/跳过整个对象,如果需要的话。
格式化:空格
JSON.stringify(value, replacer, space)
的第三个参数是要用于漂亮格式化的空格数。
以前,所有字符串化对象都没有缩进和额外的空格。如果我们想通过网络发送一个对象,这是可以的。space
参数专门用于漂亮的输出。
这里的 space = 2
告诉 JavaScript 在多行上显示嵌套对象,并在对象内缩进 2 个空格
let
user =
{
name:
"John"
,
age:
25
,
roles:
{
isAdmin:
false
,
isEditor:
true
}
}
;
alert
(
JSON
.
stringify
(
user,
null
,
2
)
)
;
/* two-space indents:
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
/* for JSON.stringify(user, null, 4) the result would be more indented:
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
第三个参数也可以是一个字符串。在这种情况下,该字符串用于缩进,而不是空格数。
space
参数仅用于记录和美观输出。
自定义“toJSON”
与用于字符串转换的 toString
类似,对象可以提供 toJSON
方法用于转换为 JSON。JSON.stringify
在可用时会自动调用它。
例如
let
room =
{
number:
23
}
;
let
meetup =
{
title:
"Conference"
,
date:
new
Date
(
Date.
UTC
(
2017
,
0
,
1
)
)
,
room
}
;
alert
(
JSON
.
stringify
(
meetup)
)
;
/*
{
"title":"Conference",
"date":"2017-01-01T00:00:00.000Z", // (1)
"room": {"number":23} // (2)
}
*/
在这里我们可以看到 date
(1)
变成了一个字符串。这是因为所有日期都具有内置的 toJSON
方法,该方法返回此类字符串。
现在让我们为我们的对象 room
(2)
添加一个自定义 toJSON
let
room =
{
number:
23
,
toJSON
(
)
{
return
this
.
number;
}
}
;
let
meetup =
{
title:
"Conference"
,
room
}
;
alert
(
JSON
.
stringify
(
room)
)
;
// 23
alert
(
JSON
.
stringify
(
meetup)
)
;
/*
{
"title":"Conference",
"room": 23
}
*/
正如我们所看到的,toJSON
既用于直接调用 JSON.stringify(room)
,也用于 room
嵌套在另一个编码对象中的情况。
JSON.parse
要解码 JSON 字符串,我们需要另一个名为 JSON.parse 的方法。
语法
let
value =
JSON
.
parse
(
str[
,
reviver]
)
;
- str
- 要解析的 JSON 字符串。
- reviver
- 可选函数 (key,value),它将针对每个
(key, value)
对调用,并且可以转换该值。
例如
// stringified array
let
numbers =
"[0, 1, 2, 3]"
;
numbers =
JSON
.
parse
(
numbers)
;
alert
(
numbers[
1
]
)
;
// 1
或对于嵌套对象
let
userData =
'{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'
;
let
user =
JSON
.
parse
(
userData)
;
alert
(
user.
friends[
1
]
)
;
// 1
JSON 可以复杂到任何程度,对象和数组可以包含其他对象和数组。但它们必须遵循相同的 JSON 格式。
以下是手写 JSON 中常见的错误(有时我们不得不为了调试目的而编写它)
let
json =
`
{
name: "John", // mistake: property name without quotes
"surname": 'Smith', // mistake: single quotes in value (must be double)
'isAdmin': false // mistake: single quotes in key (must be double)
"birthday": new Date(2000, 2, 3), // mistake: no "new" is allowed, only bare values
"friends": [0,1,2,3] // here all fine
}
`
;
此外,JSON 不支持注释。向 JSON 添加注释会使其无效。
还有另一种名为 JSON5 的格式,它允许未加引号的键、注释等。但这是一个独立的库,不在语言规范中。
常规 JSON 之所以如此严格,并不是因为其开发者偷懒,而是为了允许对解析算法进行轻松、可靠且非常快速的实现。
使用 reviver
想象一下,我们从服务器获取了一个字符串化的 meetup
对象。
它看起来像这样
// title: (meetup title), date: (meetup date)
let
str =
'{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'
;
…现在我们需要对其进行反序列化,将其转换回 JavaScript 对象。
让我们通过调用 JSON.parse
来实现
let
str =
'{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'
;
let
meetup =
JSON
.
parse
(
str)
;
alert
(
meetup.
date.
getDate
(
)
)
;
// Error!
糟糕!一个错误!
meetup.date
的值是一个字符串,而不是 Date
对象。JSON.parse
怎么知道它应该将该字符串转换为 Date
?
让我们将恢复函数作为第二个参数传递给 JSON.parse
,该函数将返回所有值“按原样”,但 date
将变为 Date
let
str =
'{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'
;
let
meetup =
JSON
.
parse
(
str,
function
(
key,
value
)
{
if
(
key ==
'date'
)
return
new
Date
(
value)
;
return
value;
}
)
;
alert
(
meetup.
date.
getDate
(
)
)
;
// now works!
顺便说一下,这同样适用于嵌套对象
let
schedule =
`
{
"meetups": [
{"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
{"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
]
}
`
;
schedule =
JSON
.
parse
(
schedule,
function
(
key,
value
)
{
if
(
key ==
'date'
)
return
new
Date
(
value)
;
return
value;
}
)
;
alert
(
schedule.
meetups[
1
]
.
date.
getDate
(
)
)
;
// works!
总结
- JSON 是一种数据格式,它有自己的独立标准和大多数编程语言的库。
- JSON 支持普通对象、数组、字符串、数字、布尔值和
null
。 - JavaScript 提供了方法 JSON.stringify 来序列化为 JSON 和 JSON.parse 来从 JSON 读取。
- 这两种方法都支持转换器函数,用于智能读/写。
- 如果对象有
toJSON
,则JSON.stringify
会调用它。