曾经,XML是互联网上传输结构化数据的事实标准。但是XML太过繁琐、冗长。为解决这个问题,JSON出现了。JSON是js的一个严格的子集,利用了js中的一些模式来表示结构化数据。关键JSON是一种数据格式,而不是一种编程语言,虽然有相同的语法形式,但JSON并不从属于js,很多语言都有针对JSON的解析器和序列化器。
语法
JSON的语法可以表示以下三种类型的值:
- 简单值:使用与js相同的语法,可以在JSON中表示字符串、数值、布尔值和null。但JSON不支持特殊值undefined。
- 对象:对象作为一种复杂数据类型,表示的是一组无序的键值对儿。而每个键值对中的值可以是简单值,也可以是复杂数据类型的值。
- 数组:数组也是一种复杂数据类型,表示一组有序的值的列表,可以通过数值索引来访问其中的值。数组的值也可以是任意类型——简单值、对象或数组。
JSON不支持变量、函数或对象实例,它就是一种表示结构化数据的格式。
简单值
最简单的JSON数据形式就是简单值。如:
5 //数值
"hello world" //字符串
js字符串与JSON字符串最大区别就是,JSON字符串必须使用双引号(单引号会导致语法错误)。
布尔值和null也是有效的JSON形式。
但是在实际应用中,JSON更多地用来表示更复杂的数据结构,而简单值只是整个数据结构中的一部分。
对象
JSON中的对象与js字面量稍微有一些不同。
下面是一个js中的字面量:
var person = {
name:"nicholas",
age:29
};
在JSON中的对象要求给属性加双引号,JSON表示形式如下:
{
"name":"nicholas",
"age":29
}
与js的对象字面量相比,JSON对象有几个地方不一样:
- 没有声明变量(JSON中没有变量的概念)。
- 没有末尾的分号(因为这并不是js语句,不用加分号)
- 对象的属性必须加双引号
属性的值可以是简单值,也可以是复杂类型值,因此可以像下面这样在对象中嵌入对象:
{
"name":"nicholas",
"age":29,
"school":{
"name":"college",
"location":"NY"
}
}
数组
JSON中的第二种复杂数据类型是数组。JSON数组采用的就是js中的数组字面量形式。
下面是js中的数组字面量:
var values = [25,"hi",true];
在JSON中,可以采用同样的语法表示同一个数组:
[25,"hi",true]
同样要注意,JSON数组也没有变量和分号。
把数组和对象结合起来,可以构成更复杂的数据集合:
[
{
"title":"aaa",
"authors":[
"aa"
],
"edition":3,
"year":2011
},
{
"title":"bbb",
"authors":[
"aa",
"bb"
],
"edition":2,
"year":2009
}
]
解析与序列化
JSON之所以流行,因为可以把JSON数据结构解析为有用的js对象。与XML数据结构要解析为DOM文档而且从中提取数据极为麻烦相比,JSON可以解析为js对象的优势极其明显。
JSON对象
早期的JSON解析器基本上就是使用js的eval()函数。由于JSON是js语法的子集,因此eval()函数可以解析、解释并返回js对象和数组。
ECMAScript5对解析JSON的行为进行规范,定义了全局对象JSON。
JSON对象有两个方法:
- stringify():把js对象序列化为JSON字符串
- parse():把JSON字符串解析为原生js值。
var book = {
"title":"aaa",
"authors":[
"aa"
],
"edition":3,
"year":2011
};
var jsonText = JSON.stringify(book);
//"{"title":"aaa","authors":["aa"],"edition":3,"year":2011}"
这个例子使用JSON.stringify( )把一个js对象序列化为一个JSON字符串,然后将它保存在变量jsonText中。
默认情况下,JSON.stringify()输出的JSON字符串不包含任何空格字符或缩进,就像上面展示的那样。
在序列化js对象时,所有函数及原型成员都会被有意忽略,不体现在结果中。此外,值为undefined的任何属性也都会被跳过。最终结果中的值都是有效的JSON数据类型。
将JSON字符串直接传递给JSON.parse()就可以得到相应的js值。
var bookCopy = JSON.parse(jsonText);
bookCopy是一个与book类似的对象。虽然book与bookCopy有相同的属性,但它们两个是独立的,没有任何关系的对象。
如果传给JSON.parse()的字符串不是有效的JSON,该方法会抛出错误。
序列化选项
实际上,JSON.stringify()除了要序列化的js对象外,还可以接受另外两个参数,这两个参数用于指定以不同的方式序列化js对象。
第一个参数是一个过滤器,可以是一个数组,也可以是一个函数。
第二个参数是一个选项,表示是否在JSON字符串中保持缩进。
过滤结果
如果过滤器参数是数组,那么JSON.stringify()的结果中将只包含数组中列出的属性。
var book = {
"title":"aaa",
"authors":[
"aa"
],
"edition":3,
"year":2011
};
var jsonText = JSON.stringify(book,["title","edition"]);
第二个参数是一个数组,包含两个字符串。这两个属性与将要序列化的对象中的属性是对应的,因此在返回的结果字符串中,就只会包含这两个属性:
"{"title":"aaa","edition":3}"
如果第二个参数是函数,行为会稍有不同。传入的函数接收两个参数,属性名和属性值。根据属性名可以知道应该要如何处理序列化的对象中的属性。属性名只能是字符串,而在并不是键值对儿的结构中,键名可以是空字符串。
为了改变序列化对象的结果,函数返回的值就是相应键的值。但是如果函数返回了undefined,那么相应的属性会被忽略。
var book = {
"title":"aaa",
"authors":[
"aa",
"bb"
],
"edition":3,
"year":2011
};
var jsonText = JSON.stringify(book,function(key,value){
switch(key){
case "authors":
return value.join(",");
case "year":
return 5000;
case "edition":
return undefined;
default:
return value;
}
});
这里,函数过滤器根据传入的键来决定结果。
如果键为“authors”,就将数组连接为一个字符串;
如果键为“year”,就将其值设置为5000;
如果键为“edition”,通过返回undefined删除该属性;
最后,一定要提供default项,此时返回传入的值,以便其他值都能正常出现在结果中。
序列化后的字符串如下:
"{"title":"aaa","authors":"aa,bb","year":5000}"
要序列化的对象中的每一个对象都要经过过滤器,因此数组中的每个带有这些属性的对象经过过滤之后,每个对象都只会包含”title”,”authors”,”year”这三个属性。
字符串缩进
JSON.stringify()方法的第三个参数用于控制结果中的缩进和空格符。
如果这个参数是一个数值,那它表示的是每个级别缩进的空格数。
例如,要在每个级别缩进4个空格:
var book = {
"title":"aaa",
"authors":[
"aa",
"bb"
],
"edition":3,
"year":2011
};
var jsonText = JSON.stringify(book,null,4);
结果如下所示:
{
"title": "aaa",
"authors": [
"aa",
"bb"
],
"edition": 3,
"year": 2011
}
可以注意到,这个方法也在结果字符串中插入了换行符以提高可读性。只要传入有效的控制缩进的参数值,结果字符串就会包含换行符。
最大缩进空格数为10,所有大于10的值都会自动转换为10.
如果缩进参数是一个字符串而非数值,则这个字符串将在JSON字符串中被用作缩进字符。在使用字符串的情况下,可以将缩进字符设置为制表符,或者两个短划线之类的任意字符。
var jsonText = JSON.stringify(book,null,"--");
{
--"title": "aaa",
--"authors": [
----"aa",
----"bb"
--],
--"edition": 3,
--"year": 2011
}
缩进字符串最长不能超过10个字符长。如果字符串长度超过了10个,结果中将只出现前10个字符。
toJSON()方法
有时候,JSON.stringify()方法还不能满足对某些对象进行自定义序列化的需求。这种情况下,可以给对象定义toJSON()方法,返回其自身的JSON数据格式。
可以为任何对象添加toJSON()方法。
var book = {
"title":"aaa",
"authors":[
"aa",
"bb"
],
"edition":3,
"year":2011,
toJSON:function(){
return this.title;
}
};
var jsonText = JSON.stringify(book); //"aaa"
以上代码在book对象上定义了一个toJSON方法,该方法返回title的值。
可以让toJSON()方法返回任何值,他都能正常工作。
比如,在这个方法上返回undefined,此时如果包含它的对象嵌入在另一个对象中,会导致它的值变为null;而如果它是顶级对象,结果就是undefined。
toJSON()可以作为函数过滤器的补充,因此理解过滤器序列化的内部顺序十分重要。假设把一个对象传入JSON.stringify(),序列化该对象的顺序如下:
- 如果存在toJSON()方法而且能通过它取得有效的值,则调用该方法,否则返回对象本身。
- 如果提供了第二个参数,应用这个函数过滤器。传入函数过滤器的值是上一步的结果。
- 对上一步返回的每个值进行相应的序列化。
- 如果提供了第三个参数,执行相应的格式化。
解析选项
JSON.parse()方法也可以接收另一个参数,该参数是一个函数,将在每个键值对上调用。
为了区别JSON.stringify()接收的过滤函数,这个函数被称为还原函数,但实际上这两个函数的签名是相同的,它们都接收两个参数,一个键和一个值,而且都要返回一个值。
如果还原函数返回undefined,则表示要从结果中删除相应的键;
如果返回其他值,则将该值插入到结果中。
var book = {
"title":"aaa",
"authors":[
"aa",
"bb"
],
"edition":3,
"year":2011,
"releaseDate":new Date(2011,11,1)
};
var jsonText = JSON.stringify(book);
var bookCopy = JSON.parse(jsonText,function(key,value){
if(key == "releaseDate"){
return new Date(value);
}else{
return value;
}
});
alert(bookCopy.releaseDate.getFullYear());
以上代码先是为book增加了一个releaseDate属性,该属性保存着一个Date对象。这个对象在序列化之后变成了有效的字符串,然后经过解析又在bookCopy中还原为一个Date对象。(还原函数在遇到“releaseDate”键时,会基于相应的值创建一个新的Date对象。)