JavaScript对象表示法(JavaScript Object Notation,简称JSON)是一种轻量级的数据交换格
式。它基于JavaScript的对象字面量表示法,那是JavaScript最精华的部分之一。尽管只是
JavaScript的一个子集,但它与语言武官。它可以被用于在所有以现代编程语言编写的程序
之间交换数据。它是一种文本格式,所以可以被人和机器阅读。它易于实现且易于使用。
1、JSON语法
JSON有6种类型的值:对象、数组、字符串、数字、布尔值(true和false)和特殊值
null。空白(空格符、制表符、回车符和换行符)可被插到任何值的前后。这可以使的JSON
文本更容易被人阅读。为了减少传输和存储的成本,空白可以被省略。
JSON对象是一个容纳“名/值”对的无序集合。名字可以是任何字符串。值可以是任何类
型的JSON值,包括数组和对象。JSON对象可以被无限层地嵌套,但一般来说保持其结构
的相对扁平是最高效的。大多数语言都有容易被映射为JSON对象的数据类型,比如对象
(object)、结构(struct)、字典(dictionary)、哈西表(hash table)、属性列表(prototype list)
或关联数组(associative array)。
JSON数组是一个值的有序序列。其值可以是任何类型的JSON值,包括数组和对象。大多
数语言都有容易被映射为JSON数组的数据类型,比如数组(array)、向量(vector)、列表
(list)或序列(sequence)。
JSON字符串要被包围在一对双引号之间。\字符被用于转义。JSON允许/字符被转义,所
以JSON可以被嵌入HTML的<script>标签中。除非用</script>标签初始化,否则HTML
不允许使用</字符序列。但JSON允许使用<\/,它能产生同样的结果却不会与HTML
相混淆。
JSON数字与JavaScript的数字相似。整数的首位不允许为0,因为一些语言用它来标示八
进制。这种基数的混乱在数据交换格式中是不可取的。数字可以是整数、实数或科学计数。
就是这样。这就是JSON的全部。JSON的设计目标是成为一个极简的轻便的和文本式的
JavaScript子集。实现互通所需要的共识越少,互通就越容易实现。
[
{
"first":"Jerome",
"middle":"Lester",
"last":"Howard",
"nick-name":"Curly",
"born":1903,
"died":1952,
"quote":"nyuk-nyuk-nyuk!"
},
{
"first":"Harry",
"middle":"Moses",
"last":"Howard",
"nick-name":"Moe",
"born":1897,
"died":1975,
"quote":"Why,you!"
},
{
"first":"Louis",
"last":"Feinberg",
"nick-name":"Larry",
"born":1902,
"died":1975,
"quote":"I'm sorry.Moe,it was an accident!"
}
]
2、安全地使用JSON
JSON特别易于用在Web应用中,因为JSON就是JavaScript。使用eval函数可以把一段
JSON文本转化成一个有用的数据结构:
var myData=eval('('+myJSONText+')');
(用圆括号把JSON文本括起来是一种避免JavaScript语法中的歧义(译注1)的变通方案。)
———————————————————————————————
译注1: 在JavaScript的语法中,表达式语句(Expression Statement)不允许以花括号“{”开
始,因为那会与块语句(Block Statements)产生混淆。在使用eval()解析JSON文本时,为了
解决此问题,可以将JSON文本套上一对圆括号。圆括号在此处作为表达式的分组运算
符,能对包围在其中的表达式进行求值。它能正确地识别对象字面量。
然而,eval函数有着骇人的安全问题。用eval去解析JSON文本安全吗?目前,在Web
浏览器中从服务器端获取数据的最佳技术是XMLHttpRequest。XMLHttpRequest只能从生成
HTML的同意服务器获取数据。使用eval解析来自那个服务器的文本安全性和解析最初
HTML的安全性一样低。那是假定该服务器存有恶意的前提下,但如果它只是存在漏洞呢?
有漏洞的服务器或许并不能正确地对JSON进行编码。如果它通过拼凑一些字符串而不是使
用一个合适的JSON编码器来创建JSON文本,那么它可能在无意间发送危险的数据。如果
它充当的是代理的角色,并且尚未确定JSON文本是否格式良好就简单地传递它,那么它可
能再次发送了危险数据。
通过使用JSON.parse(译注2)方法替代eval就能避
免这种危险。如果文本中包含任何危险数据,那么JSON.parse将抛出一个异常。为了防止
服务器出现漏洞的状况,我推荐你总是用JSON.parse替代eval。即使有一天浏览器提
供了连到其他服务器的安全数据访问,使用它同样是个好习惯。
——————————————————————————————
译注2:原生JSON支持的需求已被提交为ES3.1的工作草案,另外IE8已经提供了原生的JSON支持。
在外部数据与innerHTML进行交互时还存在另一种危险。一种常见的Ajax模式是把服务器端
发送过来的一个HTML文本片段赋值给某个HTML元素的innerHTML属性。这是一个非
常糟糕的习惯。如果这个HTML包含一个<script>标签或其等价物,那么一个恶意脚本将
被运行。这可能又是因为服务端存在漏洞。
具体有什么危险呢?如果一个恶意脚本在你的页面上被运行,它就有权访问这个页面的所
有的状态和该页面能做的操作。它能与你的服务器进行交互,而你的服务器将不能区分正
当请求和恶意请求。恶意脚本还能访问全局对象,这使得它有权访问该应用中除隐藏于闭
包中的变量之外的所有数据。它可以访问document对象,这会使它有权访问用户所能看到
的一切。它还给这个恶意脚本提供了与用户进行会话的能力。浏览器的地址栏和所有的反
钓鱼程序将告诉用户这个会话是可靠的。document对象还给该恶意脚本授权访问网络,允
许它去下载更多的恶意脚本,或者是在你的防火墙之内探测站点,或者是将它已经窃取的
隐私内容发送给世界的任何一个服务器。
这个危险是JavaScript全局变量的直接后果,它是JavaScript众多糟粕之中最糟糕的一个。
这些危险并不是由Ajax、JSON、XMLHttpRequest或Web 2.0(不管它是什么)导致的。
自从JavaScript被引入浏览器,这些危险就已经存在了,并且它将一直存在,直到JavaScript
有一天被取代。所以,请务必当心。
3、一个JSON解析器
这是一个用JavaScript编写的JSON解析器的实现方案:
var json_parse=function(){
//这是一个能把JSON文本解析成JavaScript数据结构的函数。
//它是一个简单的递归降序解析器。
//我们在另一个函数中定义此函数,以避免创建全局变量
var at, //当前字符的索引
ch, //当前字符
escapee={
'"':'"',
'\\':'\\',
'/':'/',
b:'b',
f:'\f',
n:'\n',
r:'\r',
t:'\t'
},
text,
error=function(m){
//当某处出错时,调用error。
throw{
name:'SyntaxError',
message:m,
at:at,
text:text
};
},
next=function(c){
//如果提供了参数c,那么检验它是否匹配当前字符。
if(c&&c!==ch){
error("Expected '"+c+"' instead of '"+ch+"'");
}
//获取下一个字符。当没有下一个字符时,返回一个空字符串。
ch=text.charAt(at);
at+=1;
return ch;
},
number=function(){
//解析一个数字值。
var number,
string='';
if(ch==='-'){
string='-';
next('-');
}
while(ch>='0'&&ch<='9'){
string+=ch;
next();
}
if(ch==='.'){
string+='.';
while(next()&&ch>='0'&&ch<='9'){
string+=ch;
}
}
if(ch==='e'||ch==='E'){
string+=ch;
next();
if(ch==='-'||ch==='+'){
string+=ch;
next();
}
while(ch>='0'&&ch<='9'){
string+=ch;
next();
}
}
number=+string;
if(isNaN(number)){
error("Bad number");
}else{
return number;
}
},
string=function(){
//解析一个字符串值。
var hex,
i,
string='',
ufff;
//当解析字符串值时,我们必须找到"和\字符。
if(ch==='"'){
while(next()){
if(ch==='"'){
next();
return string;
}else if(ch==='\\'){
next();
if(ch==='u'){
uffff=0;
for(i=0;i<4;i+=1){
hex=parseInt(next(),16);
if(!isFinite(hex)){
break;
}
uffff=uffff*16+hex;
}
string+=String.fromCharCode(uffff);
}else if(typeof escapee[ch]==='string'){
string+=escapee[ch];
}else{
break;
}
}else{
string+=ch;
}
}
}
error("Bad string");
},
white=function(){
while(ch&&ch<=' '){
next();
}
},
word=function(){
//true、false或null。
switch(ch){
case 't':
next('t');
next('r');
next('u');
next('e');
return true;
case 'f':
next('f');
next('a');
next('l');
next('s');
next('e');
return false;
case 'n':
next('n');
next('u');
next('l');
next('l');
return null;
}
error("Unexpected '"+ch+"'");
},
value, //值函数的占位符。
array=function(){
//解析一个数组值。
var array=[];
if(ch==='['){
next('[');
while();
if(ch===']'){
next(']');
return array; //空数组
}
while(ch){
array.push(value());
white();
if(ch===']'){
next(']');
return array;
}
next(',');
white();
}
}
error("Bad array");
},
object=function(){
//解析一个对象值。
var key,
object={};
if(ch==='{'){
next('{');
white();
if(ch==='}'){
next('}');
return object; //空对象
}
while(ch){
key=string();
white();
next(':');
object[key]=value();
white();
if(ch===')'){
next(')');
return object;
}
next(',');
white();
}
}
error("Bad object");
};
value=function(){
//解析一个JSON值。它可以是对象、数组、字符串、数字或一个词。
white();
switch(ch){
case '{':
return object();
case '(':
return array();
case '"':
return string();
case '-':
return number();
default:
return ch>='0'&&ch<='9'?number():word();
}
};
//返回json_parse函数。它将能反问上述所有的函数和变量
return function(source,reviver){
var result;
text=source;
at=0;
ch=' ';
result=value();
white();
if(ch){
error("Syntax error");
}
//如果存在reviver函数,我们就递归地对这个新结构调用walk函数,
//开始时先创造一个临时的启动对象,并以一个空字符串作为键名保存结果,
//然后传递每个“名/值”对给reviver函数去处理可能存在的转换。
//如果没有reviver函数,我们就简单地返回这个结果。
return typeof reviver==='function'?
function walk(holder,key){
var k,v,value=holder[key];
if(value&&typeof value==='object'){
for(k in value){
if(Object.hasOwnProperty.call(value,k)){
v=walk(value,k);
if(v!==undefined){
value[k]=v;
}else{
delete value[k];
}
}
}
}
return reviver.call(holder,key,value);
}({'':result},''):result;
};
}();