JSON是一种数据格式,不是一种编程语言,
虽然具有相同的语法格式,但其并不从属于JavaScript,并不是只有JS才使用JSON,毕竟JSON只是一种数据格式。很多编程语言都有针对JSON的解析器和序列化器。JSON不支持变量、函数或对象实例,它就是一种表示结构化数据的格式。
语法
JSON语法可以表示以下三种类型的值:简单值,对象,数组。
简单值:
使用与JS相同的语法可以表示字符串、数组、布尔值和null,但不支持undefined。例子:
数值5: 5
字符串:"hello world!"
JS字符串与JSON字符串最大区别是,JSON字符串必须使用双引号,单引号会导致语法错误。布尔值和null 也是有效的JSON形式。
对象:
作为一种复杂的数据类型,表示的是一组有序的键值对。键值对中的值可以是简单值,也可以是复杂类型的值。
JSON中的对象与JS字面量稍微有些不同:JSON中没有声明变量(JSON中没有变量的概念),没有末尾分号,对象的属性必须加双引号。
JS对象字面量:
var object={
name:"wf",
age:20
};
JSON表示上述对象方式如下:
{
"name":"wf",
"age":"20"
}
//属性值可无疑是简单值,也可以是复杂类型的值,可以在对象中嵌入对象
{
"name":"wf",
"age":20,
"school":{
"name":"liming",
"location":"North Andover,MA"
}
}
数组:
数组也是一种复杂数据类型,表示一组有序的值的列表,可以通过数值索引来访问其中的值。数组的值可以是任意类型-简单值,对象或数组。
JSON 数组采用的是JS中的数组字面量形式。
JS数组字面量:
var values=[25,"hi",true];
JSON中数组(无变量和分号):
[25,"hi",true]
把数组和对象结合起来可以构成更复杂的数据集合:
[
{
"title":"Professional JavaScript",
"authors":["Nicholas C.Zakas"],
"edition":3,
"year":2011
},
{
"title":"Professional JavaScript",
"authors":["Nicholas C.Zakas"],
"edition":2,
"year":2009
},
{
"title":"Professional JavaScript",
"authors":["Nicholas C.Zakas","Jeremy McPeak","Joe Fawcett"],
"edition":2,
"year":2008
}
]
这个数组包含一些图书的对象,每个对象都有几个属性,其中属性“authors”的值又是一个数组。对象和数组通常是JSON数据结构的最外层形式,利用它们可以创造出各种各样的数据结构。
解析和序列化
JSON流行的原因之一是拥有与JS类似的语法,更重要的原因是,可以把JSON数据结构解析为有用的JS对象。与XML数据结构要解析成DOM文档而且从中提取数据极为麻烦相比,JSON可以解析为JS对象的优势十分明显。
以上面包含一组图书的JSON数据结构为例,在解析为JS对象后,只需要下面以行代码就可以取得第三本书的书名:
books[2].title
这里假设把解析JSON数据结构得到的对象保存道路变量books中,下面再DOM结构中查找数据代码:
doc.getElementByTagName("book")[2].getAttribute("title")
早期的JSON解析器基本是使用JS的eval()函数。由于JSON是JS语法的子集,因此eval()函数可以解析、解释并返回JS对象和数组。ECMAScript5对解析JSON行为进行了规范,定义了全局对象JSON。
JSON对象有两个方法:stringify()和parse().最简单情况,这两个方法分别用于把JS对象序列化为JSON字符串和把JSON字符串解析为原生的JS值,例子:
var book={
title:"Professional JavaScript",
authors:["Nicholas C.Zakas"],
edition:3,
year:2011
};
var jsonText=JSON.stringify(book);
此例使用JSON.stringify()把JS对象序列化为JSON字符串,然后把它保存在变量jsonText中。默认情况下,JSON.stringify()输出的JSON字符串不包含任何空格字符或缩进,因此保存在jsonText中的字符串如下所示:
{"title":"Professional JavaScript","authors":["Nicholas C.Zakas"],"edition":3,"year":2011}
在序列化JS对象时,所有函数及原形成员都会被忽略,不体现在结果中。此外,值为undefined的任何属性也都会被跳过。结果最终都是值为有效JSON数据类型的实例属性。将JSON字符串直接传递给JSON.parse(),就可以得到相应的JS值,例如,使用如下代码就可以创建与book类似的对象:
var bookCopy=JSON.parse(json.Text)
注意,虽然book与bookCopy具有相同的属性,但它们是两个独立的、没有任何关系的对象。若传给JSON.parse()的不是有效的JSON,该方法会抛出错误。
序列化选项
JSON.stringify()除了要序列化的JS对象外,还可以接收另外两个参数,这两个参数用于指定以不同的方式序列化JS对象。第一个参数是一个过滤器,可以是一个数组,也可以是一个函数;第二个参数是一个选项,表示是否在JSON字符串中保留缩进。
若过滤器的参数是数组,则JSON.stringify()的结果中将只包含数组中列出的属性。例子:
var book={
title:"Professional JavaScript",
authors:["Nicholas C.Zakas"],
edition:3,
year:2011
};
var jsonText=JSON.stringify(book,["title","edition"]);
JSON.stringify()的第二个参数是一个数组,其中包含两个字符串:“title”和“edition”。这两个属性与将要序列化的对象中的属性是对应的,因此在返回的结果字符串中,就只会包含这两个属性:{“title”:”Professional JavaScript”,”edition”:3}
如果第二个参数是函数,传入的函数接收两个参数,属性(键)名和属性值。根据属性名可以知道应该如何处理要序列化的对象中的属性。属性名只能是字符串,而在值并非键值对结构的值时,键名可以是空字符串。函数返回的值就是相应的键值,如果函数返回了undefined,则相应的属性会被忽略。
var book={
title:"Professional JavaScript",
authors:["Nicholas C.Zakas"],
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项,此时返回传入的值,以便其他值都能挣产出现在结果中。实际上,第一次调用这个函数过滤器,传入的键是一个空字符串,而值就是book对象。序列化后的JSON字符串如下所示:{"title":"Professional JavaScript","authors":"Nicholas C.Zakas","year":5000}
字符串缩进:JSON.stringify()方法的第三个参数用于控制结果中的缩进和空白符。若此参数是一个数值,那它表示的是每个级别缩进的空格数。例如,要在每个级别缩进4个空格,可以这样写代码:
var book={
title:"Professional JavaScript",
authors:["Nicholas C.Zakas"],
edition:3,
year:2011
};
var jsonText=JSON.stringify(book,null,4);
保存在jsonText中的字符串如下所示:
{
"title":"Professional JavaScript",
"authors":["Nicholas C.Zakas"],
"edition":3,
"year":2011
}
JSON.stringify()也在结果字符串中插入了换行符以提高可读性。只要传入有效的控制缩进的参数值,结果字符串就会包含换行符。最大缩进空格数为10,所有大于10的值都会自动转换为10。
若缩进参数是一个字符串而非数值,则使用这个字符串当作缩进字符。例如:
var jsonText=JSON.stringify(book,null,"--");
这样保存在jsonText中的字符串如下所示:
{
--"title":"Professional JavaScript",
--"authors":["Nicholas C.Zakas"],
--"edition":3,
--"year":2011
}
通过对象上调用toJSON()方法,返回其自身的JSON数据格式。原生Date对象有一个toJSON()方法,能将JS中的Date对象自动转换成ISO 8601 日期字符串。
可以为任何对象添加toJSON()方法,例子:
var book={
title:"Professional JavaScript",
authors:["Nicholas C.Zakas"],
edition:3,
year:2011
toJSON:function(){
return this.title;
}
};
var jsonText=JSON.stringify(book);
以上代码在book对象上定义了一个toJSON()方法,该方法返回图书的书名。与Date对象类似,这个对象也将被序列化为一个简单的字符串而非对象。可以让toJSON()方法返回任何序列化的值,它都能正常工作。若返回undefined,此时如果包含它的对象嵌入在另一个对象中,会导致该对象的值变为null,若包含它的对象是顶级对象,结果就是undefined。
toJSON()方法作为函数过滤器的补充,理解序列化的顺序十分重要。假设把一个对象传入JSON.stringify(),序列化该对象的顺序如下:
(1)若存在toJSON()方法而且能通过它取得有效的值,则调用该方法。否则按默认顺序执行序列化。
(2)若提供了第二个参数,应用这个函数过滤器,传入函数过滤器的值是第(1)步返回的值。
(3)对第(2)步返回的值进行相应的序列化。
(4)若提供了第三个参数,执行相应的序列化。
解析选项
JSON.parse()方法也可以接收另一个参数,该参数是一个函数,将在每个键值对上调用。为了区别JSON.stringify()接收的替换(过滤)函数,这个函数被称为还原函数。实际上这两个函数的签名是相同的----他们都接收两个参数,键,值,而且都需要返回一个值。若还原函数返回undefined,表示要从结果中删除相应的键;若返回其他值,则将该值插入到结果中。将日期字符串转换为Date对象时,经常用到还原函数。例如:
var book={
title:"Professional JavaScript",
authors:["Nicholas C.Zakas"],
edition:3,
year:2011,
releaseDate:new Date(2017,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对象。这个对象在经过序列化之后变成了有效的JSON字符串,然后又经过解析在bookCopy中还原为Date对象。还原函数在遇到"releaseDate"键时,会基于相应的值创建一个新的Date对象。结果就是bookCopy.releaseDate属性中保存着一个Date对象。正因为如此,才能能够基于这个对象调用getFullYear()方法。