1.2 JSON概述
1.2.1 认识JSON
XML虽好,可作为数据交换格式,有时会喧宾夺主,标记比数据还多,徒增流量。更重要的是,在JavaScript中处理XML实在太不便利了。而JSON,没有附加的标记,在JavaScript中可作为对象处理,因而渐渐成了目前Web开发的标准数据交互格式。
JSON的英文全称是“JavaScript Object Notation”,意思就是JavaScript对象表示法。它是一种基于文本的、独立于语言的轻量级数据交换格式。它来源于ECMA-262第三版定义的JavaScript对象直接量(literal)。它不但易于阅读和编写,还易于机器解析和生成,而且完全独立于语言的文本格式,因而,JSON是一种理想的数据交换语言。
1.2.2 JSON的结构
JSON有对象和数组两种结构。
对象结构以“{”(大括号)开始,“}”(大括号)结束。中间部分由0个或多个以“,”(逗号)分隔的“关键字(key)/值(value)”列表构成,而关键字与值之间必须以“:”(冒号)分隔。其结构语法如下:
{
key1:value1,
key2:value3,
…
}
从上面的结构可以看到,这种结构的JSON有点类似其他语言中的字典或散列表。结构中的关键字是字符串,而值可以是字符串、数值、true、false、null、对象或数组。
注意 true、false和null必须全部为小写字母。当值为对象或数组时,变量记录的是对象的指针。
数组结构以“[”(中括号)开始,“]”(中括号)结束。中间部分由0个或多个以“,”分隔的值(value)列表构成,其结构语法如下:
[value1,value2,…]
值可以是字符串、数值、true、false、null、对象或数组。
注意 如果在最后一个“关键字/值”后,“}”之前有1个“,”,如“{a:1,b:2,}”在IE 8及其之前版本的浏览器会报错,而在IE 9或其他浏览器则不会报错。这也是有些程序在Firefox中运行正常,在IE 8及其之前的浏览器中不能运行的主要原因。数组结构的JSON也存在这个问题,需要特别注意。
1.2.3 JSON的例子
下面是一个JSON例子:
{
1 : "这是允许的",
"2" : "这是允许的",
"." : "这是允许的",
"中文" : "这是允许的",
count : 3,
person : [
{id:1,name:"张三"},
{id:2,name:"李四"}
],
object : {
id : 1,
msg : "对象里的对象"
}
}
从上面的例子可以看到,数字“1”也可以作为关键字。为什么呢?这是因为在JavaScript中,会自动根据需要将数字转换为字符串。从“2”、“.”和“中文”中可以看到,基本上,只要是字符串都可以作为关键字,不过笔者不建议使用这样的名称,因为这会造成对象访问上的问题,除非你永远保持使用“对象[关键字]”的方式访问对象,这会在1.2.4节中详细描述。
在“person”中,其值是一个数组结构的JSON,而该JSON又是由JSON结构的值构成的,这说明,这两种结构的JSON数据是可以嵌套使用的。而“object”值则表明JSON结构也可以作为值嵌套在JSON结构中。
1.2.4 在JavaScript中使用JSON
因为JSON是JavaScript的一个子集,所以可以在JavaScript中轻松地读取、修改JSON中的数据并向JSON中添加数据。
在开始学习下面的内容之前,请先准备一个空白页面,然后在装有Firebug的Firefox中打开该页面,最后在Firefox中打开Firebug窗口并在控制台的命令行中输入以下代码:
var obj={
1 : "这是允许的",
"2" : "这是允许的",
"." : "这是允许的",
"中文" : "这是允许的",
count : 3,
person : [
{id:1,name:"张三"},
{id:2,name:"李四"}
],
object : {
id : 1,
msg : "对象里的对象"
}
}
如果对Firebug还不熟悉,可先阅读第3章3.1节的内容。以上代码定义了一个JSON对象并赋值给变量obj。从代码中可以看到,定义一个JSON对象非常简单,只要按照数据格式把数据写在“{}”中就可以了。当然,你也可以定义一个空的JSON对象,代码如下:
var obj={};
以上代码就定义了一个空的JSON对象,在很多时候会使用到。
- 读取单个数据
在JSON中读取数据有两种方法。第一种方法是在“.”(小数点)后加上关键字。第二种方法是在中括号中包含关键字。下面我们来测试一下这两种方法。
首先在Firebug中输入的代码后面加入以下代码:
console.log(obj.1)
代码中“obj”是指向JSON对象的变量,“1”是JSON对象关键字。“console.log”是Firebug用来在控制台输出信息的命令。
单击“运行”,会看到如图1-1所示的错误信息。
这说明不能通过该方法读取关键字为数字的数据。将代码中的“1”替换为“2”、“.”,也会出现错误信息。
单击“清除”按钮清除控制台的信息后,将名称修改为“中文”,最后单击“运行”可看到如图1-2所示的信息。
这说明中文是可以直接在“.”后使用的。
如果要读取“person”中第2个对象的“name”,可将“console.log”代码修改为下面的代码:
**console.log(obj.person[1].name)**
代码中,因为“person”是数组对象,所以可通过索引访问数据。代码运行后会在控制台显示“李四”。
同样,要访问“object”中的“msg”,可执行以下代码:
console.log(obj.object.msg)
下面介绍另外一种访问方法,将输出代码修改为以下代码:
console.log(obj[1])
代码中,中括号里面的“1”是关键字。运行后可在控制台看到“这是允许的”。如果要读取名称为“.”的数据,必须将小数点用双引号包裹起来,其代码如下:
console.log(obj["."])
小数点等运算符需要使用双引号包裹是因为这些符号在语句中被当成运算符了,而不是像数字一样自动转换为字符串。
要读取“person”第2个数据中的“name”,可使用以下代码:
console.log(obj["person"][1]["name"])
当然,你也可以结合两种方法来访问数据,譬如以下代码与上面代码的作用一样:
console.log(obj["person"][1].name)
从以上的测试可以了解到,如果要读取以数字或运算符作为关键字的数据,必须使用中括号加关键字的方法,因此不是在特殊情况下,不建议使用这样的关键字,而且这样的名称不便于阅读与维护。中文名称嘛,笔者也不推荐,不过根据自己需要灵活掌握吧。
最后一点要指出的是,在代码中要保持一致的风格,以便于代码的维护。
- 遍历数据
要遍历JSON对象中的数据,可使用for…in循环。譬如要遍历上面示例中“obj”的数据,可编码如下:
for(var c in obj){
console.log(c+":",obj[c])
}
代码中,通过循环可一个个读取JSON中的关键字,并将名称保存在变量“c”中,然后可使用中括号加关键字的方法读取数据。
在Firebug中运行该循环将看到如图1-3所示的显示结果。
- 修改数据
要修改JSON对象的数据很简单,和普通变量赋值没什么区别。譬如要将示例中“count”的值修改为10,可以执行以下的代码:
obj.count=10
或
Obj["count"] = 10
4. 添加数据
向JSON对象添加数据,使用以下格式的代码就行了:
JSON_Object.key= value
或
JSON_Object[key] = value
代码中,“JSON_Object”是指向JSON的变量,“key”是关键字,“value”是值。譬如在示例中增加关键字“sex”,值为“男”的数据,代码如下:
obj.sex = "男"
或
obj["sex"] = "男"
- 删除数据
要删除JSON对象内的属性,使用delete语句就可以了。例如:
var json={name:"张三",sex:"男"}
delete json.sex
console.log(json)
执行后的结果为:
Object { name="张三"}
属性sex已被删除。
1.2.5 在.NET中使用JSON
- JSON.NET概述
当JSON逐渐成为Ajax的标准数据交互格式时,在.NET中处理JSON数据只能使用字符串拼接的方法,十分麻烦,因而催生了JSON.NET这个项目。
JSON.NET是一个免费的开源项目,大家可以登录http://json.codeplex.com/下载最新版本,本书使用的版本是4.0 release 1,本节的示例将使用该版本进行演示。
JSON.NET的功能有很多,本书主要讲述以下两个Ext JS项目常用的功能:
通过序列化方法将.NET对象转换为JSON对象。
使用LINQ to JSON读写JSON对象。
- 配置JSON.NET
在JSON.NET压缩包的bin目录下有Net、Net20、Net35、Silverlight和WindowsPhone5个目录,目录里有对应不同.Net Framework版本的库文件,根据使用的.Net Framework版本使用对应的库文件就可以了。譬如本书的例子使用的是.Net Framework 4.0版本,因而将Net35目录下的Newtonsoft.Json.Net35.dll文件添加到项目的bin目录就可以了。
要使用序列化功能,需在代码中加入以下引用代码:
using Newtonsoft.Json;
如果要使用LINQ to JSON,需在代码中加入以下引用代码:
using Newtonsoft.Json.Linq;
- 序列化
在开发Web应用时,一般都需要将数据库查询出的数据转换为JSON格式文本传送回客户端,这就需要进行序列化。在JSON.NET中,要进行序列化,常用的是JsonConvert对象的SerializeObject方法。其基本的语法格式如下:
JsonConvert.SerializeObject(object)
代码中“object”就是要序列化的.NET对象。序列化后的返回值是字符串。
下面我们通过一个例子来加深一下认识。例子主要实现的功能是将微软示例数据库“Northwnd”中客户表(Customers)的所有客户数据以JSON格式返回客户端,其代码如下:
public string Message { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
NorthwindEntities ne = new NorthwindEntities();
var q = ne.Customers.OrderBy(m=>m.CompanyName)
.Select(m=>new {
m.CustomerID,
m.CompanyName,
m.Country,
m.City,
m.Address,
m.PostalCode,
m.Phone,
m.Region
});
Message = JsonConvert.SerializeObject(q);
}
代码中,首先从实体模型中查询出数据集合“q”,然后将数据集合“q”序列化成JSON格式字符串并赋值给变量“Message”,最后在页面中输出。在浏览器中打开页面将看到以下的结果:
[{"CustomerID":"ALFKI","CompanyName":"Alfreds Futterkiste","Country":"Germany","City":"Berlin","Address":"Obere Str. 57","PostalCode":"12209","Phone":"030-0074321","Region":null},
…,
{"CustomerID":"WOLZA","CompanyName":"Wolski Zajazd","Country":"Poland","City":"Warszawa","Address":"ul. Filtrowa 68","PostalCode":"01-012","Phone":"(26) 642-7012","Region":null}]
从上面的例子可以看到,将查询数据序列化成JSON文本是一件非常简单的事。其实,对.NET对象的序列化还有很多方式,囿于篇幅,本书就不一一介绍了,有兴趣可以详细阅读JSON.NET的文档。
- LINQ to JSON
事实上,Ext JS对数据返回的格式是有一定要求的,并不是简单地返回序列化后的数据就行,这时就需要用到LINQ to JSON。LINQ to JSON的作用就是根据需要的JSON格式组织文本数据。
LINQ to JSON需要使用到JObject、JArray、JPropery和JValue 4个对象,这4个对象的详细说明如表1-1所示。
下面我们通过一个例子说明如何使用LINQ to JSON。Ext JS的所需JSON格式数据一般如下:
{
"total":5, //记录总数
"rows":[
//JSON对象格式的数据列表
]
}
示例将演示如何根据以上格式返回客户表数据,代码如下:
public string Message { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
NorthwindEntities ne = new NorthwindEntities();
var q = ne.Customers.OrderBy(m => m.CompanyName)
.Select(m => new
{
m.CustomerID,
m.CompanyName,
m.ContactName
}
).ToList();
Message = new JObject(
new JProperty("total", q.Count()), //创建记录总数
new JProperty("rows",
new JArray( //创建数据数组
from p in q
select new JObject(
new JProperty("id", p.CustomerID),
new JProperty("cpname", p.CompanyName),
new JProperty("contactName", p.ContactName)
)
)
)
).ToString();
}
从上面的代码可以看到,构建固定格式的JSON数据是相当直观的。将粗体代码与格式数据对比,可以看到,最外层的JObject创建了格式中最外层的“{}”,然后依次使用JProperty生成记录总数数据和数据列表。而代码中的JArray的作用就是生成“[]”,将使用LINQ to JSON方式生成的一个个数据对象组合成数组。本来是希望直接通过LINQ to JSON将实体模型转换成JSON的,但这样会产生“LINQ to Entities 仅支持无参数构造函数和初始值设定项”的错误,因而本示例先将查询的数据转换为列表(ToList()),再进行转换。使用LINQ to JSON可直接在select语句生成JSON数据对象,无须其他转换过程,相当方便。在使用select语句生成数据对象时,首先使用JObject生成“{}”,然后使用JProperty生成对象的数据。
在浏览器中打开页面,将看到以下的结果:
{ "total": 91, "rows": [ { "id": "ALFKI", "cpname": "Alfreds Futterkiste", "contactName": "Maria Anders" }, { "id": "ANATR", "cpname": "Ana Trujillo Emparedados y helados", "contactName": "Ana Trujillo" },
…,
{ "id": "WOLZA", "cpname": "Wolski Zajazd", "contactName": "Zbyszek Piestrzeniewicz" } ] }
注意 千万不要使用序列化的方式生成“rows”的值,如下面的代码:
new JProperty("rows",JsonConvert.SerializeObject(q)) ;
因为这样生成的“rows”值是字符串,而不是数组。
- 处理客户端提交的JSON数据
有时候,在客户端以JSON格式将数据提交到服务器比较方便。譬如,直接在Grid修改了不同行和列的数据,最后一次性将修改的数据提交到服务器端处理,这时候,使用JSON格式提交数据会很方便,例如以下提交的数据:
[
{id:"12345",title:"文章一",author:"李四"},
{id:"12367",author:"张三"},
{id:"17777",isShow:true}
]
数据表示在Grid中修改了3行数据,第1行修改了标题(title)和作者(author),第2行修改了作者,第3行修改了是否显示(isShow)。
在服务器端使用JObject或JArray的Parse方法就可轻松地将字符串转换为JSON对象,然后通过对象的方法提取数据,下面是服务器端的处理代码:
public string Message { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
string jsonString = @"
[
{id:'12345',title:'文章一',author:'李四'},
{id:'12367',author:'张三'},
{id:'17777',isShow:true}
]
";
JArray json = JArray.Parse(jsonString);
Message = @"<table border='1'>
<tr><td width='80'>ID</td><td width='100'>字段</td><td width='100'> 值</td></tr>
";
string tpl = "<tr><td>{0}</td><td>{1}</td><td>{2}</td></tr>";
foreach (JObject jobject in json)
{
foreach (var a in jobject)
{
if(a.Value.ToString()!="id")
Message += String.Format(tpl, (string)jobject["id"], a.Key, a.Value);
}
}
Message += "</table>";
在代码中,因为已知数据是使用数组形式提交的,所以采用JArray的Parse方法。
什么?你不知道数据是以数组还是对象形式提交?
这……
数据的提交方式应该是一种双方的约定,不然要处理未知的数据会很麻烦,所以不用担心这个问题。
第1层foreach循环用来获取JObject对象。第2层foreach用来获取修改过的字段名称。在这里要注意,数据默认已约定存在id这个字段。
代码运行后将看到如图1-4所示结果。
从结果可以看到,数据已经被分拆出来,这样你就可以根据id和字段去更新数据库了。
1.2.6 在Java中使用JSON
- Gson概述
Gson是谷歌的一个开源项目,其作用是在Java对象和JSON之间实现相互转换。大家可登录http://code.google.com/p/google-gson/下载最新版本,本书使用的版本是1.6版,本节的示例都将使用该版本。
Gson的功能很多,这里囿于篇幅就不一一介绍了。本节的重点是讲述如何使用Gson生成Ext JS格式的返回数据。
- 配置Gson
要使用Gson,将gson-1.6.jar文件复制到项目的“lib”目录就行了。譬如在动态Web项目中使用Gson,将文件复制到“WebContentWEB-INF”下的lib目录即可。
要引用Gson,需在引用文件中加入以下代码:
import com.google.gson.*;
你也可以根据需要细化引用。
- 使用Gson
要生成1.2.5节中介绍的JSON格式数据,需要使用JsonObject和JsonArray两个对象,这两个对象的详细说明如表1-2所示。
这两个对象是如何使用的,请看下面的代码:
String connectionUrl = "jdbc:sqlserver://192.168.0.254:1433;" +
"databaseName=Northwind;;user=sa;password=abcd-1234";
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
//使用JDBC从数据库获取数据Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
con = DriverManager.getConnection(connectionUrl);
String SQL = "SELECT CustomerID,CompanyName,ContactName " +
"FROM Customers order by CompanyName";
stmt = con.createStatement();
rs = stmt.executeQuery(SQL);
int count = 0; //计算记录总数
//构建数据列表
JsonArray array=new JsonArray();
while (rs.next()) {
//构建每行数据对象
JsonObject obj= new JsonObject();
obj.addProperty("id", rs.getString("CustomerID"));
obj.addProperty("cpname", rs.getString("CompanyName"));
obj.addProperty("contactName", rs.getString("ContactName"));
array.add(obj);
count++;
}
//构建返回格式数据
JsonObject json=new JsonObject();
json.addProperty("totals", count);
json.add("rows", array);
response.getWriter().write(json.toString());
rs.close();
}
catch (Exception e) {
response.getWriter().write(e.getMessage());
}
finally {
if (rs != null) try { rs.close(); } catch(Exception e) {}
if (stmt != null) try { stmt.close(); } catch(Exception e) {}
if (con != null) try { con.close(); } catch(Exception e) {}
}
代码中,注释“构建数据列表”之前的代码是实现数据库查询数据的,而我们的重点是JSON,所以我们的关注点是注释下面的代码。因为Gson不支持LINQ to JSON,所以我们必须一步步地构建JOSN数据。
首先创建一个JsonArray对象,准备在循环中插入数据。在while循环中,每行数据就是一个JsonObject对象,因而要创建新的JsonObject对象,然后使用addProperty方法将每列数据添加到JsonObject对象中,最后是将这个JsonObject对象使用JsonArray的add方法添加到数组中。这样,要返回的数据列表就构建完成了。
最后一步就是构建最外层的JsonObject对象,这个步骤比较简单。首先是使用addProperty方法添加记录总数,然后使用add方法将JsonArray对象作为“rows”的值添加到JsonObject对象中,最后使用toString方法转换成字符串返回客户端。在这里要注意JsonObject对象的addProperty方法和add方法的区别,addProperty方法是用来添加原生数据类型的,而add方法是用来添加JsonElement(包括JsonObject、JsonArray、JsonPrimitive和JsonNull)对象的,详细的说明可阅读Gson的API。
- 处理客户端提交的JSON数据
在Java中要处理1.2.5节中介绍的JSON数据,可使用JsonParser对象的Parse方法,具体代码如下:
response.setContentType("text/html; charset=utf-8");
String josnStr = "[" +
"{id:'12345',title:'文章一',author:'李四'},"+
"{id:'12367',author:'张三'},"+
"{id:'17777',isShow:true}"+
"]";
String tplString ="<tr><td>%1$s</td><td>%2$s</td><td>%3$s</td></tr>";
response.getWriter().write("<table border='1'>"+
"<tr><td width='80'>ID</td><td width='100'>字段</td><td width= '100'>值</td></tr>"
);
JsonParser jparser = new JsonParser();
JsonArray ja = jparser.parse(josnStr).getAsJsonArray();
for (JsonElement je : ja) {
JsonObject jo = je.getAsJsonObject();
Set<Map.Entry<String, JsonElement>> jset= je.getAsJsonObject().entrySet();
String id = jo.get("id").getAsString();
for (Map.Entry<String, JsonElement> map : jset) {
String key =map.getKey();
if(key !="id"){
response.getWriter().write(
String.format(tplString, id,key,map.getValue())
);
}
}
}
response.getWriter().write("</table>");
代码中,首先使用JsonParser对象的Parse方法将字符串转换为JsonElement对象,然后使用getAsJsonArray方法将其转换为JsonArray。通过循环从JsonArray中获取JsonObject对象。因为JsonObject没有提供获取关键字的方法,所以要将JsonObject对象转换为Set,再将Set中的数据转换为Map集合,最后使用getKey方法提取关键字。
代码运行后的结果参见图1-4。
1.2.7 更多有关JSON的信息
在本书只是简单地介绍C#和Java这两种开发语言处理JSON数据的方法,如果读者需要其他语言有关的JSON的信息,或不喜欢笔者介绍的两个JSON库,可登录http://www.json.org/json-zh.html获取更多信息。