代码改变世界

JavaScript 和 .NET 中的 JavaScript Object Notation (JSON) 简介

2007-07-18 09:11  Valens  阅读(1117)  评论(1编辑  收藏  举报


原文地址:http://msdn2.microsoft.com/zh-cn/library/bb299886.aspx

Atif Aziz,Scott Mitchell

2007 年 2 月

适用于:
JSON
Ajax

摘要:本文 JavaScript Object Notation(或 JSON),即一种开放式和基于文本的数据交换格式,它提供了一种标准数据交换格式,更适用于 Ajax 样式的 Web 应用程序。

简介

在设计要与远程计算机进行通信的应用程序时,必须选择一种数据格式和交换协议。有多种开放式标准方案可供选择,而最理想的选择取决于应用程序需求和已有的功能。例如,基于 SOAP 的 web 服务格式化数据的方式是将 XML 负载封装到 SOAP 信封中。

XML 对于许多应用场合非常好用,但在另外一些情况下则存在缺陷,使用不太理想。XML 通常使用效果不太理想的领域之一是 Ajax 风格的 web 应用程序。Ajax 是用于构建交互式 web 应用程序的技术,此类应用程序通过使用对 web 服务器的带外轻型调用来代替整页回发,从而提供了更为迅捷的用户体验。这些异步调用通过 JavaScript 在客户端进行初始化,涉及设置数据格式、将其发送到 web 服务器,以及分析和处理返回的数据。大多数浏览器可以构建、发送和分析 XML,而 JavaScript Object Notation(或 JSON)提供了一种适用于 Ajax 风格的 web 应用程序的标准数据交换格式。

JSON 是一种基于文本的开放式数据交换格式(请参见 RFC 4627)。与 XML 一样,它便于读者阅读、独立于平台,并且具有广泛的可实现性。根据 JSON 标准设置格式的数据是轻型的,可由 JavaScript 实现轻而易举地进行分析,使之成为适用于 Ajax web 应用程序的理想数据交换格式。JSON 主要是一种数据格式,因此它不局限于 Ajax web 应用程序,在任何场合,只要应用程序需要将结构化信息作为文本进行交换或存储,即可使用它。

本文探讨了 JSON 标准及其与 JavaScript 的关系,并将其与 XML 进行比较。文中讨论了用于 .NET 的开源 JSON 实现 Jayrock,并提供了许多使用 JavaScript 和 C# 创建和分析 JSON 消息的示例。

了解 JavaScript 中的文字表示法

在编程语言中,文字用于“从字面上”表达固定值,如常量整数值 4 或字符串“Hello, World”。文字可用在大多数允许使用表达式的语言中,如控制语句的部分条件、调用函数时的输入参数、变量赋值等等。例如,以下 C# 和 Visual Basic 代码使用常量整数值 42 来初始化变量 x

int x = 42;  // C#
Dim x As Integer = 42  ' Visual Basic

不同的编程语言允许使用不同类型的文字。大多数编程语言至少都支持标量类型的文字,如整数、浮点数、字符串和布尔值。对于 JavaScript 来说,有意思的一点是,除了标量类型之外,它还支持结构化类型的文字,如数组和对象。此功能允许在按需要创建和初始化数组及对象时使用简洁的语法。

JavaScript 中的数组文字由零个或多个表达式组成,每个表达式代表数组的一个元素。数组元素括在方括号内 ([]),并以逗号分隔。以下示例使用具有七大洲名称的七个字符串元素来“从字面上”定义数组:

var continents = ["Europe", "Asia", "Australia", "Antarctica", "North
America", "South America", "Africa"];
alert(continents[0] + " is one of the " + continents.length + "
continents.");

设想在没有文字表示法的 JavaScript 中创建和初始化数组的情形,将此与之比较:

var continents = new Array();
continents[0] = "Europe";
continents[1] = "Asia";
continents[2] = "Australia";
continents[3] = "Antarctica";
continents[4] = "North America";
continents[5] = "South America";
continents[6] = "Africa";

对象文字定义对象成员及成员值。对象成员及其值的列表包含在大括号 ({}) 内,成员之间以逗号分隔。在每个成员内,名称和值以冒号 (:) 分隔。以下示例创建了一个对象,并使用名为 AddressCityPostalCode 的三个成员来进行初始化,这些成员的值分别为“123 Anywhere St.”、“Springfield”和“99999”。

var mailingAddress = {
"Address"    :   "123 Anywhere St.",
"City"       :   "Springfield",
"PostalCode" :   99999
};
alert("The package will be shipped to postal code " +
mailingAddress.PostalCode);

到目前为止的这些示例说明了如何在数组和对象文字中使用字符串和数值文字。您还可以通过递归使用表示法来表示整个图表,即数组元素和对象成员值本身可以随之使用对象和数组文字。例如,以下代码段说明了一个以数组为成员的对象 (PhoneNumbers),其中该数组由一组对象组成。

var contact = {
"Name": "John Doe",
"PermissionToCall": true,
"PhoneNumbers": [
{
"Location": "Home",
"Number": "555-555-1234"
},
{
"Location": "Work",
"Number": "555-555-9999 Ext. 123"
}
]
};
if (contact.PermissionToCall)
{
alert("Call " + contact.Name + " at " + contact.PhoneNumbers[0].Number);
}
注意   在核心 JavaScript 1.5 指南中的文字部分可找到对 JavaScript 文字支持的进一步讨论。

从 JavaScript 文字到 JSON

JSON 是依据 JavaScript 中文字对象表示法的子集所创建的数据交换格式。虽然 JavaScript 可接受的文字值语法非常灵活,不过必须注意,JSON 的规则要严格得多。例如,根据 JSON 标准,对象成员的名称必须为有效的 JSON 字符串。JSON 中的字符串必须用引号括起来。而 JavaScript 则允许对象成员以引号或撇号分隔,或者完全省略引号(只要成员名称与保留的 JavaScript 关键字不冲突)。同样,JSON 中的数组元素或对象成员值仅限于一个非常有限的集合。然而,在 JavaScript 中,数组元素和对象成员值几乎可以指一切有效的 JavaScript 表达式,包括函数调用和定义!

JSON 的优点在于它的简单性。根据 JSON 标准设置格式的消息由单个顶层对象或数组组成。数组元素和对象值可以是对象、数组、字符串、数字、布尔值(true 和 false)或 null。简言之,这就是 JSON 标准!就是如此简单。有关该标准更正式的说明,请参见 www.json.orgRFC 4627

JSON 的弱点之一是缺少日期/时间文字。许多人首次接触 JSON 后,对此都感到惊讶和失望。缺少日期/时间文字的简单解释(无论能否令人安慰)是 JavaScript 也从未具备此能力:在 JavaScript 中对时间和日期值的支持完全由 Date 对象提供。因此,将 JSON 用作数据格式的大多数应用程序通常会使用字符串或数字来表示日期和时间值。如果使用的是字符串,您通常希望它是 ISO 8601 格式。如果使用的是数字,该值通常表示从 epoch 开始、以通用协调时间 (UTC) 表示的毫秒数,其中 epoch 定义为 1970 年 1 月 1 日午夜 (UTC)。同样,这仅仅是个惯例,而不是 JSON 标准的一部分。如果您要与其他应用程序交换数据,则需要检查其文档,查看它对 JSON 文字内的日期和时间值是如何进行编码的。例如,Microsoft 的 ASP.NET AJAX 使用的就不是上述任一种惯例。它代之以将 .NET DateTime 值编码为 JSON 字符串,字符串内容是 \/Date(ticks)\/,其中 ticks 表示从 epoch (UTC) 开始的毫秒数。因此,UTC 时间 1989 年 11 月 29 日 4:55:30 AM 将编码为“\/Date(628318530718)\/”。

ASP.NET AJAX:在 JSON 日期和时间字符串内

ASP.NEXT 中的 AJAX JSON 序列化程序将 DateTime 实例编码为 JSON 字符串。在预发布周期中,ASP.NET AJAX 使用格式“@ticks@”,其中 ticks 表示从通用协调时间 (UTC) 1970 年 1 月 1 日起经过的毫秒数。以 UTC 表示的日期和时间(如 1989 年 11 月 29 日 4:55:30 AM)会编码为“@62831853071@”。虽然简单易懂,但此格式无法区分序列化的日期和时间值与看起来像序列化日期但又不需要进行反序列化的值。因此,ASP.NET AJAX 团队对最终版本进行了更改,通过采用“\/Date(ticks)\/”格式解决了这一问题。

新格式借助一个小技巧减少了误解的可能性。在 JSON 中,字符串中的正斜杠 (/) 字符可以用反斜杠 (\) 进行转义(即使没有对此进行严格要求)。ASP.NET AJAX 团队利用这点修改了 JavaScriptSerializer,将 DateTime 实例编写为字符串“\/Date(ticks)\/”。两个正斜杠的转义只是表面的,但对 JavaScriptSerializer 至关重要。按照 JSON 规则,\/Date(ticks)\/ 在技术上相当于 /Date(ticks)/,但 JavaScriptSerializer 会将前者反序列化为 DateTime,将后者反序列化为 String。因此与预发布版本的“@ticks@”格式相比,混淆的可能性会大大减少。

比较 JSON 与 XML

JSON 和 XML 均能够以基于文本、便于读者阅读的数据交换格式来表示本机内存中对象。此外,两种数据交换格式是同构的,只要文本是一种格式,即可得出另外一种格式的等效文本。例如,调用 可公开访问的 Yahoo! web 服务之一时,您可以通过 querystring 参数指明响应需要设置为 XML 还是 JSON 格式。因此,决定数据交换格式时,不是简单的选此或彼作为银弹的问题,而是要看哪种格式具有成为特定应用程序的最佳选择的“特征”。例如,XML 起源于标记文档文本,而且有继续在该领域发扬光大的趋势(XHTML 就是很好的证明)。而 JSON 则起源于编程语言类型和结构,因此提供了更加自然和便于使用的交换结构化数据映射。除了这两个起点之外,下表可帮助您了解和比较 XML 与 JSON 的重要特征。

XML 与 JSON 之间的重要特征区别

特征 XML JSON
数据类型 不提供任何数据类型概念。必须依靠 XML 架构来添加类型信息。 提供标量数据类型,并能够通过数组和对象表示结构化数据。
数组支持 必须按照惯例表示数组,例如通过使用外部占位符元素,将数组内容模型化为内部元素。通常,外部元素使用内部元素所用名称的复数形式。 本机数组支持
对象支持 必须按照惯例表示对象,通常通过混合使用属性和元素。 本机对象支持
null 支持 需要对 XML 实例文档中的元素使用 xsi:nil,加上导入对应的命名空间。 在本机识别 null 值。
注释 本机支持,通常通过 API 来提供。 不支持。
命名空间 支持命名空间,消除了组合文档时的名称冲突风险。命名空间还允许对现有基于 XML 的标准进行安全地扩展。 无命名空间概念。通常通过嵌套对象或在对象成员名称中使用前缀(实际操作中以前者优先),来避免命名冲突。
格式设置决定 复杂。需要更多精力来决定如何将应用程序类型映射到 XML 元素和属性。会引起热烈争论,讨论以元素为中心还是以属性为中心的方法更好。 简单。为应用程序数据提供更直接的映射。唯一例外是缺少日期/时间文字。
大小 文档有些冗长,尤其在使用以元素为中心的方法来设置格式时。 语法非常简洁,产生的格式化文本中的大部分空间(理所当然)由所代表的数据使用。
在 JavaScript 中分析 需要 XML DOM 实现和其他应用程序代码,将文本映射回 JavaScript 对象。 不需要其他应用程序代码便能够分析文本,可以使用 JavaScript 的 eval 函数。
学习曲线 通常需要同时使用多种技术:XPath、XML 架构、XSLTXML 命名空间DOM 等。 非常简单的技术层次,为具有 JavaScript 或其他动态编程语言背景的开发人员所熟悉。

JSON 是相对较新的数据交换格式,尚不具备 XML 目前所拥有的多年使用经历和供应商支持(虽然 JSON 正在快速赶超)。下表重点说明了 XML 和 JSON 领域中一些事件的当前状态。

XML 与 JSON 之间的支持区别

支持 XML JSON
工具 具备由众多业界供应商广泛提供的一组成熟工具。 尚不具备丰富的工具支持,如编辑器和格式化程序。
Microsoft .NET Framework .NET Framework 1.0 之后的版本具有非常优秀和成熟的支持。XML 支持作为基类库 (BCL) 的一部分来提供。对于非托管环境,MSXML 可发挥用武之地。 目前尚无此功能,作为 ASP.NET AJAX 一部分的最初实现除外。
平台和语言 以众多平台和语言(商业和开源实现)提供了各种广泛使用的分析器和格式化程序。 在众多平台上以多种语言提供了分析程序和格式化程序。有关完整的参考资料,请查询 json.org。目前的大多数实现都可能是开源项目。
集成语言 业界供应商当前正在试验语言内的“字面”支持。有关详细信息,请参见 Microsoft 的 LINQ 项目 仅在 JavaScript/ECMAScript 中提供本机支持。
注意 上表均不是比较点的完整列表。其实还可以从更多角度比较两种数据格式,但我们认为这些关键点足以构成一个初步印象。

使用 JavaScript 创建和分析 JSON 消息

将 JSON 用作数据交换格式时的两个常见任务是,将本机内存中的表示转换为其 JSON 文本表示,反之亦然。遗憾的是,在撰写文本时,JavaScript 不提供可从给定对象或数组创建 JSON 文本的内置函数。这些方法预计将包含在 2007 年第四版的 ECMAScript 标准中。在 JSON 格式化函数正式添加到 JavaScript 并广泛用于常见实现之前,请使用可从 http://www.json.org/json.js 下载的参考实现脚本。

在撰写本文时的最新更新中,www.json.org 上的 json.js 脚本将 toJSONString() 函数添加到数组、字符串、布尔值、对象和其他 JavaScript 类型。该标量类型(如数字和布尔值)的 toJSONString() 函数相当简单,因为它们只需返回实例值的字符串表示形式。例如,如果值为 true,Boolean类型的 toJSONString() 函数返回字符串“true”,否则返回“false”。数组和对象类型的 toJSONString() 函数则更有意思。对于 Array 实例,会依次调用每个所包含元素的 toJSONString() 函数,结果会以逗号进行连接从而分隔每个结果。最终输出会包括在方括号内。同样,对于 Object 实例,会枚举每个成员,并调用其 toJSONString() 函数。成员名称及其值的 JSON 表示形式在中间用冒号连接;每个成员名称和值对以逗号分隔,整个输出会包括在大括号内。

toJSONString() 函数的实际结果是,使用单个函数调用可将所有类型转换为其 JSON 格式。以下 JavaScript 使用详细和非文字方法(用于说明目的),创建了一个 Array 对象并特意添加了七个 String 元素。然后接着显示数组 JSON 表示形式:

// josn.js must be included prior to this point
var continents = new Array();
continents.push("Europe");
continents.push("Asia");
continents.push("Australia");
continents.push("Antarctica");
continents.push("North America");
continents.push("South America");
continents.push("Africa");
alert("The JSON representation of the continents array is: " +
continents.toJSONString());

图 1. toJSONString() 函数生成根据 JSON 标准设置格式的数组。

分析 JSON 文本甚至更加简单。由于 JSON 只是 JavaScript 文字的子集,因此可以使用将源 JSON 文本视为 JavaScript 源代码的 eval(expr) 函数来分析内存中表示形式。eval 函数接受输入有效 JavaScript 代码的字符串,并计算表达式结果。因此,下面一行代码是将 JSON 文本转换为本机表示形式所需的全部代码:

var value = eval( "(" + jsonText + ")" );
注意 使用额外的圆括号可使 eval 将来源输入无条件地视为表达式。这对于对象来说尤其重要。如果您尝试使用包含 JSON 文本的字符串调用 eval,其中该文本定义了对象,如字符串“{}”(表示空对象),那么它只会返回未定义作为分析结果。圆括号会强制 JavaScript 分析器将顶层大括号视为 Object 实例的文字表示法,而不是定义语句块的大括号。同样,如果顶层项是一个数组,如 eval("[1,2,3]"),也不会发生同样的问题。然而,出于一致性需要,在调用 eval 之前 JSON 文本应始终用圆括号括起来,以便分析来源时不会产生歧义。

对文字表示法计算结果后,会返回与文字语法对应的实例,并将其赋值给 value。请看下面的例子:它使用 eval 函数分析数组的文字表示法,并将得到的数组赋值给变量 continents

var arrayAsJSONText = '["Europe", "Asia", "Australia", "Antarctica",
"North America", "South America", "Africa"]';
var continents = eval( arrayAsJSONText );
alert(continents[0] + " is one of the " + continents.length + "
continents.");

当然,实际操作中被计算结果的 JSON 文本会来自一些外部源,而不是像上述情况中被硬编码。

eval 函数会盲目地对它传递的任一表达式计算结果。不可靠的来源会因此包含具潜在危险的 JavaScript,附带于或混入组成 JSON 数据的文字表示法。在来源不受信任的情况下,强烈建议您使用 parseJSON() 函数(位于 json.js 中)对 JSON 文本进行分析:

// Requires json.js
var continents = arrayAsJSONText.parseJSON();

parseJSON() 函数也使用 eval,但仅限于 arrayAsJSONText 中包含的字符串符合 JSON 文本标准时。它通过一个更智能的正则表达式测试来完成这一操作。

在 .NET Framework 中使用 JSON

JSON 文本可依据 JavaScript 代码轻松地进行创建和分析,这是它的一个吸引人之处。但是,当 JSON 用于 ASP.NET web 应用程序时,只有浏览器受 JavaScript 支持,因为服务器端代码很可能是用 Visual Basic 或 C# 编写的。

大多数为 ASP.NET 设计的 Ajax 库都支持以编程方式创建和分析 JSON 文本。因此,要在 .NET 应用程序中使用 JSON,请考虑使用其中一个库。有众多的开源和第三方方案可供选择,而 Microsoft 也有自己的 Ajax 库,名为 ASP.NET AJAX

在本文中,我们会介绍使用 Jayrock 的示例,它是一种用于 Microsoft .NET Framework 的 JSON 开源实现,由合著者 Atif Aziz 创建。我们选择使用 Jayrock 而非 ASP.NET AJAX 的原因有三个:

  • Jayrock 是开源的,可以按需要扩展或自定义。
  • Jayrock 可用于 ASP.NET 1.x,、2.0 和 Mono 应用程序,而 ASP.NET AJAX 仅限于 ASP.NET 2.0 版。
  • Jayrock 的作用域仅限于 JSON 和 JSON-RPC,前者是本文的重点内容。ASP.NET AJAX 包括对创建和分析 JSON 文本的一些支持,但它的主要用途是提供一个丰富的平台,在 ASP.NET 中构建端到端 Ajax 风格的 web 应用程序。您的重点内容为 JSON 时,额外的点缀性功能反而会让人分神。

在 .NET 中通过 Jayrock 使用 JSON,与在 .NET Framework 中通过 XmlWriterXmlReaderXmlSerializer 类使用 XML 相似。位于 Jayrock 中的类 JsonWriterJsonReaderJsonTextWriterJsonTextReader 模拟 .NET Framework 类 XmlWriterXmlReaderXmlTextWriterXmlTextReader 的语义。这些类在低级别和面向流的级别与 JSON 交互时非常有用。使用这些类,可通过一系列的方法调用逐个创建或分析 JSON 文本。例如,使用 JsonWriter 类方法 WriteNumber(number) 可根据 JSON 标准写出适当的“数字”形式的字符串表示形式。JsonConvert 类提供了用于在 .NET 类型和 JSON 之间进行转换的 ExportImport 方法。这些方法提供了分别与 XmlSerializer 类方法 SerializeDeserialize 相似的功能。

创建 JSON 文本

以下代码说明了如何使用 JsonTextWriter 类来创建洲字符串数组的 JSON 文本。此 JSON 文本会发送到传入构造函数的 TextWriter 实例,在此示例中正好是来自控制台的输出流(在 ASP.NET 中您可以使用 Response.Output):

using (JsonTextWriter writer = JsonTextWriter(Console.Out))
{
writer.WriteStartArray();
writer.WriteString("Europe");
writer.WriteString("Asia");
writer.WriteString("Australia");
writer.WriteString("Antarctica");
writer.WriteString("North America");
writer.WriteString("South America");
writer.WriteString("Africa");
writer.WriteEndArray();
}

除了 WriteStartArrayWriteStringWriteEndArray 方法之外,JsonWriter 类还提供了用于编写其他 JSON 值类型的方法,如 WriteNumberWriteBooleanWriteNull 等。WriteStartObjectWriteEndObjectWriteMember 方法为对象创建 JSON 文本。以下示例说明了如何为“了解 JavaScript 中的文字表示法”部分探讨的联系对象创建 JSON 文本:

private static void WriteContact()
{
using (JsonWriter writer = new JsonTextWriter(Console.Out))
{
writer.WriteStartObject();              //  {
writer.WriteMember("Name");             //      "Name" :
writer.WriteString("John Doe");         //          "John Doe",
writer.WriteMember("PermissionToCall"); //      "PermissionToCall"
:
writer.WriteBoolean(true);              //          true,
writer.WriteMember("PhoneNumbers");     //      "PhoneNumbers" :
writer.WriteStartArray();               //          [
WritePhoneNumber(writer,                //            {
"Location": "Home",
"Home", "555-555-1234");            //              "Number":
"555-555-1234" },
WritePhoneNumber(writer,                //            {
"Location": "Work",
"Work", "555-555-9999 Ext. 123");   //              "Number":
"555-555-9999 Ext. 123" }
writer.WriteEndArray();                 //          ]
writer.WriteEndObject();                //  }
}
}
private static void WritePhoneNumber(JsonWriter writer, string location,
string number)
{
writer.WriteStartObject();      //  {
writer.WriteMember("Location"); //      "Location" :
writer.WriteString(location);   //          "...",
writer.WriteMember("Number");   //      "Number" :
writer.WriteString(number);     //          "..."
writer.WriteEndObject();        //  }
}

JsonConvert 类中的 ExportExportToString 方法可用于将指定的 .NET 类型序列化为 JSON 文本。例如,不需要使用 JsonTextWriter 类为七大洲数组手动构建 JSON 文本,以下对 JsonConvert.ExportToString 的调用即可产生相同的结果:

string[] continents = {
"Europe", "Asia", "Australia", "Antarctica", "North America", "South
America", "Africa"
};
string jsonText = JsonConvert.ExportToString(continents);

分析 JSON 文本

JsonTextReader 类提供了各种分析 JSON 文本令牌的方法,其中核心的一种是 Read。每次调用 Read 方法时,分析器会使用下一个令牌,可能是字符串值、数字值、对象成员名称、数组开头等。可能的话,可以通过 Text 属性访问当前令牌的已分析文本。例如,如果该读取器位于 Boolean 数据中,则 Text 属性会根据实际分析值返回“true”或“false”。

以下示例代码使用 JsonTextReader 类,对包含七大洲名称的字符串数组的 JSON 文本表示形式进行分析。每个以字母“A”开头的洲会发送到控制台:

string jsonText = @"["Europe", "Asia", "Australia", "Antarctica",
"North America", "South America", "Africa"]";
using (JsonTextReader reader = new JsonTextReader(new
StringReader(jsonText)))
{
while (reader.Read())
{
if (reader.TokenClass == JsonTokenClass.String &
reader.Text.StartsWith("A"))
{
Console.WriteLine(reader.Text);
}
}
}
注意Jayrock 中的 JsonTextReader 类是一个非常自由的 JSON 文本分析器。它实际上允许的语法要比 RFC 4627 中列出的规则所规定的有效 JSON 文本多得多。例如,就象在 JavaScript 中一样,JsonTextReader 类允许单行和多行注释出现在 JSON 文本内。单行注释以双斜杠 (//) 开头,多行注释以斜杠星号 (/*) 开头,并以星号斜杠 (*/) 结尾。单行注释甚至能以井字号 (#) 开头,这在 Unix 样式的配置文件中十分常见。在所有实例中,分析器会完全跳过注释,不会通过 API 公开。和在 JavaScript 中一样,JsonTextReader 允许以撇号 (') 分隔 JSON 字符串。该分析器甚至可以容忍最后一个对象成员或者数组元素后面多余的逗号。
即使具备所有这些附加内容,JsonTextReader 仍是符合标准的分析器!而 JsonTextWriter 则只能产生严格符合标准的 JSON 文本。这遵循了通常所说的可靠性原则,即“严以律己,宽以待人”。

要将 JSON 文本直接转换为 .NET 对象,请使用 JsonConvert 类导入方法,指定输出类型和 JSON 文本。以下示例显示了从 JSON 字符串数组到 .NET 字符串数组的转换:

string jsonText = @"["Europe", "Asia", "Australia", "Antarctica",
"North America", "South America", "Africa"]";
string[] continents = (string[]) JsonConvert.Import(typeof(string[]),
jsonText);

以下是一个更有意思的转换示例:取得 RSS XML 源,使用 XmlSerializer 将其反序列化为 .NET 类型,然后使用 JsonConvert 将该对象转换为 JSON 文本(将 XML 格式的 RSS 有效转换为 JSON 文本):

XmlSerializer serializer = new XmlSerializer(typeof(RichSiteSummary));
RichSiteSummary news;
// Get the MSDN RSS feed and deserialize it...
using (XmlReader reader = XmlReader.Create("http://msdn.microsoft.com/rss.xml"))
{
news = (RichSiteSummary) serializer.Deserialize(reader);
}
// Export the RichSiteSummary object as JSON text, emitting the output to
Console.Out
using (JsonTextWriter writer = new JsonTextWriter(Console.Out))
{
JsonConvert.Export(news, writer);
}
注意 可在本文附带的示例中找到 RichSiteSummary 定义及其相关类型。

在 ASP.NET 中使用 JSON

我们已经介绍了如何在 JavaScript 中、以及通过 Jayrock 在 .NET Framework 中使用 JSON,接下来我们来看一个关于在何处以及如何应用这些知识的实际示例。考虑 ASP.NET 2.0 的客户端脚本回调功能,它可简化 web 浏览器向 ASP.NET 页面(或向页面中的特定控件)发出带外调用的过程。在典型的回调情形中,浏览器中的客户端脚本将数据打包并回送到 web 服务器,由服务器端方法进行某些处理。从服务器收到响应数据后,客户端会用它来更新浏览器显示。

注意 可在《MSDN 杂志》的文章“ASP.NET 2.0 中的脚本回调”中找到更多信息。

客户端回调情形中的难题在于,客户端和服务器只能来回运送一个字符串。因此,待交换的信息必须在发送前从本机内存中的表示形式转换为字符串,然后在收到后从字符串分析回本机内存中的表示形式。ASP.NET 2.0 中的客户端脚本回调功能不要求进行交换的数据使用特定字符串格式,也不提供在本机内存中和字符串表示之间进行转换的任何内置功能;开发人员可以依据所选择的数据交换格式来实现转换逻辑。

以下示例说明了如何在客户端脚本回调情形中将 JSON 用作数据交换格式。特别是,该示例由 ASP.NET 页面组成,此页面使用 Northwind 数据库中的数据,以下拉列表形式提供类别列表;选定类别中的产品则显示在项目符号列表中(请参见图 3)。每当客户端更改下拉列表时,将发生回调并传入唯一元素为选定 CategoryID 的数组。

注意 我们传入的是包含选定 CategoryID 作为其唯一元素的数组(而不仅仅是 CategoryID),因为 JSON 标准要求任何 JSON 文本都必须有对象或数组作为其根。当然,客户端不需要向服务器传递 JSON 文本,在此示例中本来可以只将选定的 CategoryID 作为字符串进行传递。但是,我们想要演示在回调的请求和响应消息中发送 JSON 文本。

Page_Load 事件处理程序的以下代码配置了 Categories DropDownList Web 控件,以便在它发生更改时调用 GetProductsForCategory 函数,并传递选定的下拉列表值。如果传入的下拉列表值大于零,此函数会初始化客户端脚本回调:

// Add client-side onchange event to drop-down list
Categories.Attributes["onchange"] = "Categories_onchange(this);";
// Generate the callback script
string callbackScript = ClientScript.GetCallbackEventReference(
/* control        */ this,
/* argument       */ "'[' + categoryID + ']'",
/* clientCallback */ "showProducts",
/* context        */ "null");
// Add the Categories_onchange function
ClientScript.RegisterClientScriptBlock(GetType(),
"Categories_onchange", @"
function Categories_onchange(sender)
{
clearResults();
var categoryID = sender.value;
if (categoryID > 0)
{
" + callbackScript + @"
}
}", true);

ClientScriptManager 类中的 GetCallBackEventReference 方法用于生成可调用回调的 JavaScript 代码,具有以下签名:

public string GetCallbackEventReference (
Control control,
string argument,
string clientCallback,
string context,
)

argument 参数指定回调期间哪些数据会从客户端发送到 web 服务器,clientCallback 参数则指定在回调完成时调用的客户端函数的名称 (showProducts)。GetCallBackEventReference 方法调用会生成以下 JavaScript 代码,并将它添加到呈现的标记中:

WebForm_DoCallback('__Page','[' + categoryID +
']',showProducts,null,null,false)

'[' + categoryID + ']' 是回调期间传递到服务器的值(具有单个元素 categoryID 的数组),showProducts 则是回调返回时执行的 JavaScript 函数。

在服务器端,响应回调而执行的方法使用 Jayrock 的 JsonConvert 类来分析传入的 JSON 文本,并设置传出 JSON 文本的格式。特别值得注意的是,与选定类别相关的产品的名称都作为一个字符串数组进行检索并返回。

// Deserialize the JSON text into an array of integers
int[] args = (int[]) JsonConvert.Import(typeof(int[]), eventArgument);
// Read the selected CategoryID from the array
int categoryID = args[0];
// Get products based on categoryID
NorthwindDataSet.ProductsRow[] rows = Northwind.Categories.FindByCategoryID(categoryID).GetProductsRows();
// Load the names into a string array
string[] productNames = new string[rows.Length];
for (int i = 0; i < rows.Length; i++)
{
productNames[i] = rows[i].ProductName;
}// Serialize the string array as JSON text and return it to the clientreturn JsonConvert.ExportToString(productNames);
注意JsonConvert 类使用了两次——第一次是将 eventArgument 中的 JSON 文本转换为整数数组,第二次是将该字符串数组 productNames 转换为 JSON 文本,以返回到客户端。或者,我们也可以使用此处的 JsonReaderJsonWriter 类,但在涉及数据相对较小并易于映射到现有类型的情况下,JsonConvert 可以将同样的工作做得相当好。

数据从服务器端返回时,会调用依据 GetCallBackEventReference 方法指定的 JavaScript 函数,并传递返回值。此 JavaScript 方法 showProducts 以引用 <div> 元素 ProductOutput 开始。然后它会分析 JSON 响应,并动态添加无序列表,每个数组元素一个列表项。如果没有返回选定类别的产品,则会显示相应的消息。

function showProducts(arg, context)
{
// Dump the JSON text response from the server.
document.forms[0].JSONResponse.value = arg;
// Parse JSON text returned from callback.
var categoryProducts = eval("(" + arg + ")");
// Get a reference to the <div> ProductOutput.
var output = document.getElementById("ProductOutput");
// If no products for category, show message.
if (categoryProducts.length == 0)
{
output.appendChild(document.createTextNode("There are no products
for this category..."));
}
else
{
// There are products, display them in an unordered list.
var ul = document.createElement("ul");
for (var i = 0; i < categoryProducts.length; i++)
{
var product = categoryProducts[i];
var li = document.createElement("li");
li.appendChild(document.createTextNode(product));
ul.appendChild(li);
}
output.appendChild(ul);
}
}

图 2 说明了事件顺序,图 3 显示了此示例的运作过程;完整的代码包括在本文下载中。

图 2:客户端将选定的 CategoryID 作为数组中的单个元素进行发送,服务器则返回相关产品名称的数组。

图 3:产品显示在选定类别内的项目符号列表中。

结束语

JSON 是一种基于文本的轻型数据交换格式,以 JavaScript 编程语言中文字表示法的子集为基础。它提供了用于应用程序数据结构的简洁编码,通常用于一种或两种应用程序交换数据都可以使用 JavaScript 实现的情况,如 Ajax 风格的 web 应用程序。JSON 的吸引人之处在于简单易懂、便于采用和实现。JSON 对于已经熟悉 JavaScript 或同样支持丰富文字表示法的其他编程语言(如 Python 和 Ruby)的开发人员来说,实际上没有学习曲线。只需调用 eval 函数,即可完成对 JavaScript 代码中 JSON 文本的分析;使用 http://www.json.org/json.js 上提供的 json.js 脚本,即可轻松创建 JSON 文本。

有许许多多的库可帮助在所有主要的平台和框架中使用 JSON。本文中我们介绍了 Jayrock,它是用于在 .NET 应用程序中创建和分析 JSON 文本的开源库。Jayrock 可用于 ASP.NET 1.x、2.0 和 Mono 应用程序。ASP.NET AJAX 提供相似的 JSON 功能,但仅限于 ASP.NET 2.0 应用程序。

快乐编程!

Ajax 还是 AJAX?

Ajax 这个术语最初由 Jesse James Garrett 设想出来,用于描述 web 应用程序风格和创建高度交互的 web 应用程序所需的一组技术。从前,术语 Ajax 作为 Asynchronous JavaScript And XML(异步 JavaScript 和 XML)的缩写 AJAX 在 web 上传播。但随着时间的变化,人们意识到 AJAX 中的“X”无法形象地代表与后台 web 服务器进行通信时使用的基础数据格式,因为大多数实现都转向 JSON 作为更简单而有效的替代方案。因此,没有人提出如 AJAJ(有些拗口)之类的替代缩写,该缩写基本上已引退,而倾向于采用“Ajax 术语”而非“AJAX 缩写”。

在撰写本文时,希望看到混合及广泛使用的“AJAX”和“Ajax”具有同一含义。在本文中,我们坚持“Ajax 术语”。但是,如果商业产品要提供启用 Ajax 样式应用程序的框架,则需要使用缩写形式来区分与其名称类似的洗涤剂产品,避免可能的商标或法律争端。

参考资料

特别感谢

在本文提交给 MSDN 之前,有许多人自愿帮助我们校对文章,并对内容、语法和方向等方面提出了宝贵意见。审校过程的主要参与者有 Douglas Crockford、Eric Schönholzer 和 Milan Negovan。

作者简介

Atif Aziz 是 Skybow AG 的首席顾问,他的主要职责是帮助客户了解和构建 .NET 开发平台上的解决方案。Atif 通过会议演讲以及为技术出版物撰写文章,经常为微软开发人员社区出力。他是一位 INETA 发言人,并且是瑞士最大的 .NET 用户组的总裁。您可以通过他的网站 http://www.raboof.com/ 与他联系。

Scott Mitchell 是六部 ASP/ASP.NET 书籍的作者及 4GuysFromRolla.com 网站的创立者。自 1998 年以来,他一直从事 Microsoft web 技术方面的工作。Scott 是一位独立技术顾问、培训专家兼作家。可通过他的博客与其联系:http://ScottOnWriting.net