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。它可以是
一个 节点访问器(请参阅 通过节点访问器转换数据),用于在字符串化之前转换值树。例如
functionreplacer(key,value){if(typeofvalue==='number'){value=2*value;}returnvalue;}
使用 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()
varobj={toJSON:function(key){// Use JSON.stringify for nicer-looking outputconsole.log(JSON.stringify(key));return0;}};
如果使用 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 是一个 节点访问器(请参阅 通过节点访问器转换数据),可用于转换解析后的数据。在此示例中,我们将日期字符串转换为 日期对象:
functiondateReviver(key,value){if(typeofvalue==='string'){varx=Date.parse(value);if(!isNaN(x)){// valid date string?returnnewDate(x);}}returnvalue;}
以下是交互过程
> 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)。让我们使用名称 节点访问器 来表示您传入的转换函数。这些方法迭代树并为每个节点调用访问器。然后,它可以选择替换或删除该节点。节点访问器的签名为
functionnodeVisitor(key,value)
参数为
this
key
key 始终是一个字符串。值
根节点 root 没有父节点。访问 root 时,会为其创建一个伪父节点,并且参数具有以下值
this 为 { '': root }。key 为 ''。value 为 root。节点访问器有三个选项来返回值
value。然后不执行任何更改。undefined。然后删除该节点。以下是一个节点访问器的示例。它记录已传递给它的值。
functionnodeVisitor(key,value){console.log([// Use JSON.stringify for nicer-looking outputJSON.stringify(this),// parentJSON.stringify(key),JSON.stringify(value)].join(' # '));returnvalue;// 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'