JSON(JavaScript 对象表示法)是一种用于数据存储的纯文本格式。它作为 Web 服务、配置文件等的数据交换格式已经变得非常流行。ECMAScript 5 提供了一个 API,用于在 JSON 格式的字符串和 JavaScript 值之间进行转换(解析)和反向转换(字符串化)。
本节介绍 JSON 是什么以及它是如何创建的。
JSON 将数据存储为 纯文本。它的语法是 JavaScript 表达式语法的子集。例如:
{
"first"
:
"Jane"
,
"last"
:
"Porter"
,
"married"
:
true
,
"born"
:
1890
,
"friends"
:
[
"Tarzan"
,
"Cheeta"
]
}
JSON 使用 JavaScript 表达式中的以下结构
null
它遵循以下规则
'mystr'
)是非法的。 Douglas Crockford 于 2001 年发现了 JSON。他给它起了个名字,并在 http://json.org 上发布了规范
我发现了 JSON。我并没有声称发明了 JSON,因为它本来就存在于自然界中。我所做的只是发现了它,给它起了个名字,并描述了它的用途。我并没有声称自己是第一个发现它的人;我知道还有其他人至少在我之前一年就发现了它。我发现的最早的例子是,早在 1996 年,Netscape 就有人使用 JavaScript 数组字面量进行数据通信,这至少比我偶然想到这个想法早了五年。
最初,Crockford 想将 JSON 命名为 JavaScript 标记语言,但 JSML 这个缩写已经被 JSpeech 标记语言 占用。
JSON 规范已被翻译成多种人类语言,现在有许多编程语言的库支持解析和生成 JSON。
Douglas Crockford 制作了一张 JSON 名片,正面是徽标(见 图 22-1),背面是完整的语法(见 图 22-2)。这从视觉上清楚地表明了 JSON 是多么简单。
语法可以转录如下
{
}
{
成员 }
键值对
键值对 ,
成员
:
值
[
]
[
元素 ]
值
值 ,
元素
字符串
数字
对象
数组
true
false
null
""
"
字符 "
字符
字符 字符
除-"-或-\-或-控制字符之外的任何 Unicode 字符
\" \\ \/ \b \f \n \r \t
\u
四个十六进制数字
整数
整数 小数部分
整数 指数部分
整数 小数部分 指数部分
数字
数字1-9 数字
-
数字
-
数字1-9 数字
.
数字数字
数字 数字
e e+ e-
E E+ E-
全局变量 JSON
充当生成和解析包含 JSON 数据的字符串的函数的命名空间。
JSON.stringify(value, replacer?, space?)
将 JavaScript 值 value
转换为 JSON 格式的字符串。它有两个可选参数。
可选参数 replacer
用于在字符串化之前更改 value
。它可以是
一个 节点访问器(请参阅 通过节点访问器转换数据),用于在字符串化之前转换值树。例如
function
replacer
(
key
,
value
)
{
if
(
typeof
value
===
'number'
)
{
value
=
2
*
value
;
}
return
value
;
}
使用 replacer
> JSON.stringify({ a: 5, b: [ 2, 8 ] }, replacer) '{"a":10,"b":[4,16]}'
一个属性键白名单,用于隐藏所有键不在列表中的属性(非数组对象的)。例如
> JSON.stringify({foo: 1, bar: {foo: 1, bar: 1}}, ['bar']) '{"bar":{"bar":1}}'
白名单对数组没有影响
> JSON.stringify(['a', 'b'], ['0']) '["a","b"]'
可选参数 space
影响输出的格式。如果没有此参数,则 stringify
的结果是单行文本
> console.log(JSON.stringify({a: 0, b: ['\n']})) {"a":0,"b":["\n"]}
使用它时,将插入换行符,并且通过数组和对象的每个嵌套级别都会增加缩进。有两种方法可以指定如何缩进
将数字乘以缩进级别,并按空格数缩进该行。小于 0 的数字将被解释为 0;大于 10 的数字将被解释为 10
> console.log(JSON.stringify({a: 0, b: ['\n']}, null, 2)) { "a": 0, "b": [ "\n" ] }
要缩进,请为每个缩进级别重复一次给定的字符串。仅使用字符串的前 10 个字符
> console.log(JSON.stringify({a: 0, b: ['\n']}, null, '|--')) { |--"a": 0, |--"b": [ |--|--"\n" |--] }
因此,以下对 JSON.stringify()
的调用将一个对象打印为格式良好的树
JSON
.
stringify
(
data
,
null
,
4
)
在对象中,JSON.stringify()
仅考虑可枚举的自有属性(请参阅 属性特性和属性描述符)。以下示例演示了不可枚举的自有属性 obj.foo
被忽略:
> var obj = Object.defineProperty({}, 'foo', { enumerable: false, value: 7 }); > Object.getOwnPropertyNames(obj) [ 'foo' ] > obj.foo 7 > JSON.stringify(obj) '{}'
JSON.stringify()
如何处理 JSON 不支持的值(例如函数和 undefined
)取决于它在何处遇到它们。不支持的值本身会导致 stringify()
返回 undefined
而不是字符串
> JSON.stringify(function () {}) undefined
值为不支持的值的属性将被忽略
> JSON.stringify({ foo: function () {} }) '{}'
数组中不支持的值将被字符串化为 null
> JSON.stringify([ function () {} ]) '[null]'
如果 JSON.stringify()
遇到具有 toJSON
方法的对象,它将使用该方法获取要字符串化的值。例如:
> JSON.stringify({ toJSON: function () { return 'Cool' } }) '"Cool"'
日期已经有一个 toJSON
方法,该方法生成一个 ISO 8601 日期字符串:
> JSON.stringify(new Date('2011-07-29')) '"2011-07-28T22:00:00.000Z"'
toJSON
方法的完整签名如下
function
(
key
)
key
参数允许您根据上下文进行不同的字符串化。它始终是一个字符串,指示在父对象中找到您的对象的位置
我将通过以下对象演示 toJSON()
var
obj
=
{
toJSON
:
function
(
key
)
{
// Use JSON.stringify for nicer-looking output
console
.
log
(
JSON
.
stringify
(
key
));
return
0
;
}
};
如果使用 JSON.stringify()
,则 obj
的每次出现都将替换为 0
。toJSON()
方法会收到通知,指示在属性键 'foo'
和数组索引 0 处遇到了 obj
> JSON.stringify({ foo: obj, bar: [ obj ]}) "foo" "0" '{"foo":0,"bar":[0]}'
内置的 toJSON()
方法如下:
Boolean.prototype.toJSON()
Number.prototype.toJSON()
String.prototype.toJSON()
Date.prototype.toJSON()
JSON.parse(text, reviver?)
解析text
中的 JSON 数据并返回一个 JavaScript 值。以下是一些示例:
> JSON.parse("'String'") // illegal quotes SyntaxError: Unexpected token ILLEGAL > JSON.parse('"String"') 'String' > JSON.parse('123') 123 > JSON.parse('[1, 2, 3]') [ 1, 2, 3 ] > JSON.parse('{ "hello": 123, "world": 456 }') { hello: 123, world: 456 }
可选参数 reviver
是一个 节点访问器(请参阅 通过节点访问器转换数据),可用于转换解析后的数据。在此示例中,我们将日期字符串转换为 日期对象:
function
dateReviver
(
key
,
value
)
{
if
(
typeof
value
===
'string'
)
{
var
x
=
Date
.
parse
(
value
);
if
(
!
isNaN
(
x
))
{
// valid date string?
return
new
Date
(
x
);
}
}
return
value
;
}
以下是交互过程
> var str = '{ "name": "John", "birth": "2011-07-28T22:00:00.000Z" }'; > JSON.parse(str, dateReviver) { name: 'John', birth: Thu, 28 Jul 2011 22:00:00 GMT }
JSON.stringify()
和 JSON.parse()
都允许您通过传入一个函数来转换 JavaScript 数据:
JSON.stringify()
允许您在将 JavaScript 数据转换为 JSON 之前对其进行更改。JSON.parse()
解析 JSON,然后允许您对生成的 JavaScript 数据进行后处理。JavaScript 数据是一个树,其复合节点是数组和对象,其叶子节点是原始值(布尔值、数字、字符串、null
)。让我们使用名称 节点访问器 来表示您传入的转换函数。这些方法迭代树并为每个节点调用访问器。然后,它可以选择替换或删除该节点。节点访问器的签名为
function
nodeVisitor
(
key
,
value
)
参数为
this
key
key
始终是一个字符串。值
根节点 root
没有父节点。访问 root
时,会为其创建一个伪父节点,并且参数具有以下值
this
为 { '': root }
。key
为 ''
。value
为 root
。节点访问器有三个选项来返回值
value
。然后不执行任何更改。undefined
。然后删除该节点。以下是一个节点访问器的示例。它记录已传递给它的值。
function
nodeVisitor
(
key
,
value
)
{
console
.
log
([
// Use JSON.stringify for nicer-looking output
JSON
.
stringify
(
this
),
// parent
JSON
.
stringify
(
key
),
JSON
.
stringify
(
value
)
].
join
(
' # '
));
return
value
;
// don't change node
}
让我们使用此函数来检查 JSON 方法如何迭代 JavaScript 数据。
特殊的根节点首先出现,采用前缀迭代(父节点在子节点之前)。第一个被访问的节点始终是伪根节点。每次调用后显示的最后一行是 stringify()
返回的字符串:
> JSON.stringify(['a','b'], nodeVisitor) {"":["a","b"]} # "" # ["a","b"] ["a","b"] # "0" # "a" ["a","b"] # "1" # "b" '["a","b"]' > JSON.stringify({a:1, b:2}, nodeVisitor) {"":{"a":1,"b":2}} # "" # {"a":1,"b":2} {"a":1,"b":2} # "a" # 1 {"a":1,"b":2} # "b" # 2 '{"a":1,"b":2}' > JSON.stringify('abc', nodeVisitor) {"":"abc"} # "" # "abc" '"abc"'
叶子节点首先出现,采用后缀迭代(子节点在父节点之前)。最后一个被访问的节点始终是伪根节点。每次调用后显示的最后一行是 parse()
返回的 JavaScript 值:
> JSON.parse('["a","b"]', nodeVisitor) ["a","b"] # "0" # "a" ["a","b"] # "1" # "b" {"":["a","b"]} # "" # ["a","b"] [ 'a', 'b' ] > JSON.parse('{"a":1, "b":2}', nodeVisitor) {"a":1,"b":2} # "a" # 1 {"a":1,"b":2} # "b" # 2 {"":{"a":1,"b":2}} # "" # {"a":1,"b":2} { a: 1, b: 2 } > JSON.parse('"hello"', nodeVisitor) {"":"hello"} # "" # "hello" 'hello'