代码改变世界

JSON

2011-02-28 09:21  沐海  阅读(1701)  评论(0编辑  收藏  举报

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;
   };
}();


记录生活、工作、学习点滴!
E-Mail:mahaisong@hotmail.com 欢迎大家讨论。
沐海博客园,我有一颗,卓越的心!