JavaScript-JSON-秘籍-全-

JavaScript JSON 秘籍(全)

原文:zh.annas-archive.org/md5/7BFA16E9EEE620D98CFF9D2379355647

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

JavaScript 对象表示法(JSON)迅速成为 Web 上结构化文档交换的通用语言,在很多领域中超越了 XML。这个成功的三个原因是很明显的:它与 JavaScript 配合良好,它简单,而且它 just works。然而,它成功的其他原因也很多。正如你将在本书的页面中看到的,它得到了各种语言和库的广泛支持,使得在各种场景下使用它变得非常容易。

在本书中,我提供了 JSON 常见用途的菜谱。你可以从头到尾阅读这本书,了解 JSON 在构建网络和独立应用程序中的所有用途。然而,它被组织成一本菜谱书,这样你可以快速查找解决当前问题的章节或菜谱。我建议快速浏览一下序言,了解内容分布,根据你的兴趣快速浏览第一章,客户端 JSON 的读写,或第二章,服务器端 JSON 的读写,然后直接跳到你最感兴趣的菜谱。

本书涵盖内容

第一章,客户端 JSON 的读写,提供了在多种客户端环境中读写 JSON 的菜谱,包括 JavaScript、C#、C++、Java、Perl 和 Python。

第二章,服务器端 JSON 的读写,采取了相反的视角,审视了 Clojure、C#、Node.js、PHP 和 Ruby 等典型服务器端语言中的 JSON。当然,你也可以用这些语言编写客户端应用程序,正如你也可以用 C#或 Java 编写服务器一样。因此,这些章节之间菜谱的划分有些任意性;选择一种语言,深入研究!

第三章,在简单的 AJAX 应用程序中使用 JSON,向你展示了如何将 JSON 应用于当今浏览器的数据交换。

第四章,使用 JSON 在 jQuery 和 AngularJS 的 AJAX 应用程序中,讨论了如何使用 JSON 与两个流行的网络框架,jQuery 和 Angular。

第五章,使用 JSON 与 MongoDB,向你展示了 MongoDB,一种流行的 NoSQL 数据库,如何使用 JSON 作为其存储文档格式,并提供了使用 MongoDB 作为网络应用程序中的 REST 服务的菜谱。

第六章,使用 JSON 与 CouchDB,向你展示了 CouchDB,另一种流行的 NoSQL 数据库,如何使用 JSON,以及如何在你的网络应用程序中使用 CouchDB 作为独立的 REST 服务。

第七章, 以类型安全的方式使用 JSON, 探讨了你如何可以适应 JSON 的无类型性质,利用 C#,Java 和 TypeScript 等语言提供的类型安全,以减少你应用程序中的编程错误。

第八章, 使用 JSON 进行二进制数据传输, 展示了你如何能够使用 JSON,即使它是一个基于文本的文档格式,如果你需要这样做,你仍然可以使用它来移动二进制数据。

第九章, 使用 JSONPath 和 LINQ 查询 JSON, 有关于如何针对 JSON 文档编写查询,以获取你正在寻找的数据片段的菜谱。这与第五章, 使用 JSON 与 MongoDB 和 第六章, 使用 JSON 与 CouchDB 的菜谱结合时尤其强大。

第十章, JSON 在移动平台上的应用, 展示了使用 Android,iOS 和 Qt 的移动应用程序中使用 JSON 的菜谱。

本书你需要什么

与许多其他技术书籍不同,这本书专注于在其示例中涵盖广泛的支持技术。我不期望你立即就有经验或工具来尝试这本书中的每一个示例,尤其是。然而,列出几件事情是有帮助的。

你应该有一些编程经验,最好是 JavaScript。除非一个菜谱针对特定的编程语言,如 C#,这本书中的菜谱都是用 JavaScript 编写的。我这样做有两个原因。首先,因为 JSON 中的"J"代表 JavaScript(尽管它广泛适用于其他语言),而且,在当今时代,每个程序员至少应该对 JavaScript 有一个基本的了解。

就软件环境而言,一开始,你应该能够访问一个好的网络浏览器,如 Chrome,或者最近版本的 Safari,Firefox 或 Internet Explorer。你可以使用这些浏览器中的 JavaScript 运行时来尝试 JSON 并开始入门。

其次,很多客户端-服务器示例都使用了 Node.js。我选择 Node.js 进行服务器端示例编程,因为它也是 JavaScript,这意味着你在客户端和服务器之间移动时不需要跳过不同的语言语法。Node.js 在 Windows,Mac OS X 和 Linux 上运行得也很好,所以你应该不会遇到设置问题。

如果你对使用 JSON 与数据库感兴趣,CouchDB 或 MongoDB 是你的最佳选择,我在本书中讨论了这两者。你选择哪一个真的取决于你的领域和个人喜好。我在各种项目中使用了 5 年的 MongoDB,但最近喜欢上了 CouchDB 的一些功能和它对 RESTful 服务的集成支持。

最后,如果你是微软开发者,你可能会想特别注意本书中使用 Newtonsoft 的 Json.NET 的 C#示例。Json.NET 就是 C#中 JSON 应该的样子,它绝对值得你关注。

本书适合谁

如果你正在编写将结构化数据从一个地方移动到另一个地方的应用程序,这本书就是为你准备的。这尤其正确,如果你一直在使用 XML 来完成工作,因为完全有可能你用更少的代码和更少的数据开销在 JSON 中完成同样的工作。

尽管本书的章节在应用程序的客户端和服务器方面做了一些区分,但如果你是前端、后端或全栈开发者,都没有关系。使用 JSON 的原则适用于客户端和服务器,事实上,理解方程两边的开发人员通常会创建最好的应用程序。

章节

在本书中,你会找到几个经常出现的标题(准备,如何做,如何工作,还有更多,以及也见)。

为了清楚地说明如何完成一个食谱,我们按照以下方式使用这些节:

准备

本节告诉你食谱中会有什么内容,以及如何设置食谱所需的任何软件或任何初步设置。

如何做到…

本节包含遵循食谱所需的步骤。

它是如何工作的…

本节通常包括对上一节发生事情的详细解释。

还有更多…

本节包括关于食谱的额外信息,以使读者更加了解食谱。

也见

本节提供了对食谱其他有用信息的帮助性链接。

约定

在本书中,你会找到一些区分不同信息种类的文本样式。以下是这些样式的一些示例及其含义的解释。

文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、假 URL、用户输入和 Twitter 处理方式如下所示:"让我们进一步看看loads和dumps。"

代码块如下所示:

function doAjax() {
var xmlhttp;
  if (window.XMLHttpRequest)
  {
    // code for IE7+, Firefox, Chrome, Opera, Safari
    xmlhttp=new XMLHttpRequest();
  }
}

当我们希望将你的注意力吸引到代码块的某个特定部分时,相关的行或项目将被设置为粗体:

function doAjax() {
var xmlhttp;
  if (window.XMLHttpRequest)
  {
    // code for IE7+, Firefox, Chrome, Opera, Safari
 xmlhttp=new XMLHttpRequest();
  }
}

任何命令行输入或输出如下所示:

# cp /usr/src/asterisk-addons/configs/cdr_mysql.conf.sample
 /etc/asterisk/cdr_mysql.conf

新术语和重要词汇以粗体显示。例如在菜单或对话框中看到的屏幕上的词,在文本中如下所示:"然后,你可能想要去更多工具 | JavaScript 控制台。"

注意

警告或重要说明以这样的框出现。

技巧

技巧和窍门像这样出现。

读者反馈

来自我们读者的反馈总是受欢迎的。让我们知道您对这本书的看法——您喜欢或不喜欢什么。读者反馈对我们来说很重要,因为它帮助我们开发出您会真正从中受益的标题。

要向我们发送一般性反馈,只需发送电子邮件<feedback@packtpub.com>,并在消息主题中提及书籍的标题。

如果您在某个话题上有专业知识,并且有兴趣撰写或为书籍做出贡献,请查看我们的作者指南:www.packtpub.com/authors。

客户支持

现在您已经成为 Packt 书籍的骄傲拥有者,我们有很多事情可以帮助您充分利用您的购买。

下载示例代码

您可以从www.packtpub.com下载您购买的所有 Packt Publishing 书籍的示例代码文件。如果您在其他地方购买了此书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

错误

虽然我们已经尽一切努力确保我们的内容的准确性,但错误确实会发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——我们将非常感谢您能向我们报告。这样做,您可以节省其他读者的挫折感,并帮助我们改进本书的后续版本。如果您发现任何错误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击错误提交****表单链接,并输入您错误的详细信息。一旦您的错误得到验证,您的提交将被接受,错误将被上传到我们的网站,或添加到该标题的错误部分现有的错误列表中。

要查看以前提交的错误,请前往www.packtpub.com/books/content/support,在搜索框中输入书籍名称。所需信息将在错误部分出现。

版权侵犯

版权材料的网上侵犯是一个持续存在的问题,涵盖所有媒体。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上以任何形式发现我们作品的非法副本,请立即提供给我们地址或网站名称,以便我们可以寻求解决方案。

如果您发现有疑似侵犯版权的材料,请通过<copyright@packtpub.com>联系我们。

我们感谢您在保护我们的作者和我们提供有价值内容的能力方面所提供的帮助。

问题

如果您与此书有任何问题,您可以联系<questions@packtpub.com>我们,我们会尽力解决问题。

第一章 客户端读写 JSON

在本章中,我们将介绍以下菜谱:

  • 在 JavaScript 中读写 JSON

  • 在 C++ 中读写 JSON

  • 在 C# 中读写 JSON

  • 在 Java 中读写 JSON

  • 在 Perl 中读写 JSON

  • 在 Python 中读写 JSON

除了在 Python 中读写 JSON 之外,我们首先向您展示 JSON 格式的简要回顾,以帮助为本书后续内容奠定基础。

简介

JSON 代表 JavaScript Object Notation(JavaScript 对象表示法)。它是一种开放标准,用于将数据表示为带值的属性。最初来源于 JavaScript 语法(因此得名)用于作为更冗长和结构化的 Extensible Markup Language(XML)的替代,在网页应用程序中使用,现在它被用于许多独立和网络应用程序中的数据序列化和传输。

JSON 提供了在客户端和服务器之间封装数据的理想方式。在本章中,你将学习如何在章节开始时指定的语言中使用 JSON。

这些语言通常用于客户端开发,这正是我们将要关注的内容。我们将在第二章中更多地了解服务器端语言,服务器端读写 JSON。

让我们来看一下由 web API 返回的 JSON 数据,该 API 的网址是 www.aprs.fi,我对其进行了少许修改以使示例更清晰(在后面的第四章中,使用 JSON 在 jQuery 和 AngularJS 的 AJAX 应用程序中,你将学习如何使用网络浏览器和 JavaScript 自己获取这些数据):

{
  "command":"get",
  "result":"ok",
  "what":"loc",
  "found":2,
  "entries":[
    {
      "class":"a",
      "name":"KF6GPE",
      "type":"l",
      "time":"1399371514",
      "lasttime":"1418597513",
      "lat":37.17667,
      "lng":-122.14650,
      "symbol":"\/-",
      "srccall":"KF6GPE",
    },
    {
      "class":"a",
      "name":"KF6GPE-7",
      "type":"l",
      "time":"1418591475",
      "lasttime":"1418591475",
      "lat":37.17633,
      "lng":-122.14583,
      "symbol":"\\K",
      "srccall":"KF6GPE-7",
    }
  ]
}

提示

下载示例代码

你可以从 www.packtpub.com 下载你购买的所有 Packt Publishing 书籍的示例代码文件。如果你在其他地方购买了这本书,你可以访问 www.packtpub.com/support 并注册,以电子邮件方式直接接收这些文件。

这个例子有几个需要注意的地方:

  • 数据组织成属性和值,每个属性由冒号分隔。(注意,JSON 文档也可以是一个单独的值,比如字符串、浮点数、整数或布尔值。)

  • 属性作为双引号括起来的字符串出现在冒号的左侧。

  • 值位于冒号的右侧,可以是以下内容:

    • 字符串(用双引号括起来的,例如 KF6GPE

    • 数字(整数或浮点数),例如 2 或 37.17667

    • 数组(由逗号分隔的值,包含在方括号中),例如 entries 的值

    • 由更多属性和值组成的全局对象,例如 entries 值中的两个数组值

    • 另外(虽然这个例子没有显示),布尔值 true 和 false

  • 请注意,许多其他类型的值,如日期/时间对或单个字符,JSON 是不支持的。

  • 虽然这个例子不完全清楚,但空格是无关紧要的。例如,没有必要将每一对都放在单独的一行上,缩进是完全任意的。

JSON 的属性名-属性值属性,以及嵌套值和表示数组的能力,赋予了 JSON 很大的灵活性。你可以使用 JSON 表示很多常见的对象,包括大多数不包含大量二进制数据的对象(有关如何使用 JavaScript 和 JSON 表示二进制数据的思路,请参见第八章,使用 JSON 进行二进制数据传输)。这包括原始值(自文档化,因为每个值都伴随着一个属性),具有简单值的平面对象,包括地图,以及简单或复杂对象的数组。

JSON 的自文档化特性使其成为数据传输的理想选择,即便它不支持 XML 中可能找到的注释。它所具有的纯文本特性使其在网络上使用诸如gzip这样的流行压缩方案进行压缩变得容易,而且与更冗长的 XML 相比,它的格式对人类阅读更为友好。

提示

请注意,JSON 文档本质上是一种树结构,因此,它不支持循环数据结构,比如图,其中节点指向数据结构中的另一个节点。

如果你使用编程语言的本地区域表示创建此类数据结构,并尝试将其转换为 JSON,你会得到一个错误。

在 JavaScript 中读写 JSON

JSON 最初是一种在 Web 服务器和 JavaScript 之间传输数据的手段,因此让我们从一个简单的代码片段开始,该代码片段在 Web 浏览器中使用 JavaScript 读写 JSON。我们将在第四章,使用 JSON 在 AJAX 应用程序中与 jQuery 和 AngularJS 一起使用中展示一个使用 AJAX 和 JSON 的 Web 应用程序的全部内容;以下是如何从 JSON 获取 JavaScript 对象以及如何从 JavaScript 对象创建 JSON 字符串。

准备就绪

你需要一种方法来编辑 JavaScript 并在浏览器中运行它。在本例中,以及本书中的几乎所有示例,我们将使用 Google Chrome 来完成这个任务。你可以在www.google.com/chrome/browser下载 Google Chrome。一旦你安装了 Google Chrome,你希望通过点击右侧的定制和控制 Doodle Chrome图标来激活 JavaScript 控制台,它看起来像这样:

准备就绪

然后,你需要前往更多工具 | JavaScript 控制台。你应该能在网页的侧面看到一个 JavaScript 控制台,就像这样:

准备就绪

如果你喜欢使用快捷键,你也可以在 Windows 和 Linux 上使用Ctrl + Shift + J,或者在 Macintosh 上使用control + option + J。

从这里开始,你可以在右下角输入 JavaScript 代码并按下Enter键(在 Mac OS X 系统上为return键)来执行 JavaScript。

如何做到...

现代网页浏览器,如 Chrome,在 JavaScript 运行时定义了一个 JSON 对象,该对象可以将包含 JSON 的字符串数据转换为 JavaScript 对象,反之亦然。这是一个简单的示例:

>var json = '{"call":"KF6GPE","type":"l","time":
"1399371514","lasttime":"1418597513","lat":37.17667,"lng":
-122.14650,"result" : "ok" }';
<- "{ "call":"KF6GPE","type":"l","time":"1399371514",
"lasttime":"1418597513","lat":37.17667,"lng":-122.14650,
"result" : "ok" }"
>var object = JSON.parse(json);
<- Object {call:"KF6GPE",type:"l",time:"1399371514",
lasttime:"1418597513",lat:37.17667, lng:-122.14650,result: "ok"}
> object.result
<- "ok"
>var newJson = JSON.stringify(object);
<- "{ "call":"KF6GPE","type":"l","time":"1399371514",
"lasttime":"1418597513","lat": 37.17667,"lng": -122.14650,
"result" : "ok" }"

注意

在此及随后的 JavaScript 示例中,你在 JavaScript 控制台输入的文本前面有一个>符号,而 JavaScript 控制台打印的内容是以<-符号开头的。

它是如何工作的...

Chrome 和其他现代网页浏览器定义了JSON对象,该对象具有将包含 JSON 的字符串和 JavaScript 对象之间相互转换的方法。

在前一个示例中,我们首先将json变量的值设置为一个包含一个名为result的属性的简单 JSON 表达式,其值为ok。JavaScript 解释器返回变量json的结果值。

下一行使用了JSON对象的parse方法,将json引用的 JSON 字符串转换为 JavaScript 对象:

>var object = JSON.parse(json);
<- Object { call:"KF6GPE", type:"l", time:"1399371514", lasttime:"1418597513", lat:37.17667, lng:-122.14650, result: "ok"}

然后,你可以像访问任何其他 JavaScript 对象一样访问对象中的任何一个值;毕竟,它就是一个对象:

> object.result;
<- "ok"

最后,如果你需要将一个对象转换成 JSON 格式,你可以使用JSON对象的stringify方法来实现:

>var newJson = JSON.stringify(object);
<- "{ "call":"KF6GPE","type":"l","time":"1399371514",
"lasttime":"1418597513","lat": 37.17667,"lng": -122.14650,
"result" : "ok" }"

还有更多内容...

关于这些方法,你应该知道两件事情。首先,如果传递给 parse 的 JSON 格式不正确,或者根本不是 JSON,它会抛出一个异常:

>JSON.parse('{"result" = "ok" }')
<- VM465:2 Uncaught SyntaxError: Unexpected token =

错误信息不是很有帮助,但如果你在调试由不完全符合规范且未经调试的 JSON 编码器发送的 JSON,这总比没有强。

第二,非常旧的网页浏览器可能没有包含这些方法的 JSON 对象。在这种情况下,你可以使用 JavaScript 函数eval,在将 JSON 用括号括起来后再对其进行处理,像这样:

>eval('('+json+')')
<- Object {result: "ok"}

eval函数评估你传递给它的 JavaScript 字符串,而 JSON 表示实际上只是 JavaScript 的一个子集。然而,尽管理论上你可以避免使用eval,但有几个原因建议你这么做。首先,它通常比JSON对象提供的方法慢。其次,它不安全;你传递的字符串可能包含恶意的 JavaScript,这可能会导致你的 JavaScript 应用程序崩溃或被其他方式破坏,这绝不是轻视的威胁。尽可能使用JSON对象。第三,你可以使用parse和stringify方法来处理简单值,比如布尔值、数字和字符串;你不仅仅限于前一个示例中的键值对。如果我只想传递一个布尔值(比如"交易成功!"),我可能会直接写如下内容:

var jsonSuccess = 'true';
<- "true"
> var flag = JSON.parse(jsonSuccess);

最后,值得指出的是,JSON 的parse和stringify方法都接受一个可选的替换函数,该函数在序列化或反序列化被序列化或反序列化的对象中的每个键和值时被调用。你可以使用这个函数在 JSON 被解析时进行实时数据转换;例如,你可以使用它将日期字符串表示和自纪元开始以来午夜的秒数之间进行转换,或者纠正字符串的大小写。我可以在以下代码中使用替换函数进行转换,使调用字段小写:

> var object = JSON.parse(json, function(k, v) {
  if ( k == 'call') return v.toLowerCase();
});
<- Object { call:"kf6gpe", type:"l", time:"1399371514",
lasttime:"1418597513", lat:37.17667, lng:-122.14650, result: "ok"}

你还可以返回undefined以从结果中移除一个项目;为了从生成的 JSON 中省略类型字段,我可以执行以下操作:

> var newJson = JSON.stringify(object, function (k, v) {
  if k == 'type') return undefined;
});
<- "{ "call":"KF6GPE","time":"1399371514","lasttime":
"1418597513","lat": 37.17667,"lng": -122.14650, "result" : "ok" 
}"

在 C++中读写 JSON

C++是一种早在 JSON 出现之前就存在的语言,但对于许多项目来说仍然相关。C++中没有对 JSON 的原生支持,但有许多库提供了对 JSON 工作的支持。或许最广泛使用的是JsonCpp,可在 GitHub 上找到github.com/open-source-parsers/jsoncpp。它的许可证为 MIT 许可证或如果你愿意的话为公共领域,所以它的使用几乎没有限制。

准备

要使用 JsonCpp,你首先需要前往网站下载包含整个库的压缩文件。一旦你这么做,你需要将其与你的应用程序源代码集成。

你将它在应用程序源代码中集成的方法因平台而异,但一般过程是这样的:

  1. 使用网站上的说明创建库的合并源和头文件。为此,你需要下载 JsonCpp 并安装 Python 2.6 或更高版本。从 JsonCpp 的顶级目录运行python amalgamate.py。

  2. 在任何你想使用 JsonCpp 库的文件中包含dist/json/json.h头文件。

  3. 在你的项目 Makefile 或构建系统中包含源文件dist/jsoncpp.cpp。

一旦你这样做,你应该在任何包含json/json.h头文件的文件中访问 JsonCpp 接口。

如何进行操作...

下面是一个简单的 C++应用程序,它使用 JsonCpp 将包含一些简单 JSON 的std::string和 JSON 对象之间进行转换:

#include <string>
#include <iostream>
#include "json/json.h"

using namespace std;

int main(int argc, _TCHAR* argv[])
{
  Json::Reader reader;
  Json::Value root;

  string json = "{\"call\": \"KF6GPE\",\"type\":\"l\",\"time\":
  \"1399371514\",\"lasttime\":\"1418597513\",\"lat\": 37.17667,
  \"lng\": -122.14650,\"result\":\"ok\"}";

  bool parseSuccess = reader.parse(json, root, false);

  if (parseSuccess)
  {
    const Json::Value resultValue = root["result"];
    cout << "Result is " << resultValue.asString() << "\n";
  }

  Json::StyledWriter styledWriter;
  Json::FastWriter fastWriter;
  Json::Value newValue;
  newValue["result"] = "ok";

  cout << styledWriter.write(newValue) << "\n";
  cout << fastWriter.write(newValue) << "\n";

  return 0;
}

它是如何工作的...

这个例子开始于包含必要的包含文件,包括定义 JsonCpp 接口的json/json.h。我们明确引用std命名空间以简化问题,尽管对于Json命名空间,其中 JsonCpp 定义了所有其接口,不要这样做。

JsonCpp 实现定义了 Json::Reader 和 Json::Writer,分别指定 JSON 读取器和写入器的接口。实践中,Json::Reader 接口也是 JSON 类的实现,可以读取 JSON,将其值返回为 Json::Value。Json::Writer 变量只是定义了一个接口;你可能需要使用其子类,如 Json::FastWriter 或 Json::StyledWriter,从 Json::Value 对象创建 JSON。

前一个列表首先定义了 Json::Reader 和 Json::Value;我们将使用读取器读取我们接下来定义的 JSON,并将其值存储在 Json::Value 变量 root 中。(假设你的 C++ 应用程序会从其他来源获取 JSON,比如网络服务或本地文件。)

解析 JSON 只需调用读取器的 parse 函数,将 JSON 和将要写入 JSON 值的 Json::Value 传递给它。它返回一个布尔值,如果 JSON 解析成功,则为 true。

Json::Value 类将 JSON 对象表示为树;个别值通过原始 JSON 的属性名称来引用,这些值是这些键的值,可以通过诸如 asString 之类的方法访问,该方法将对象的值作为本地 C++ 类型返回。Json::Value 这些方法包括以下内容:

  • asString, 它返回 std::string。

  • asInt, 它返回 Int。

  • asUInt, 它返回 UInt。

  • asInt64, 它返回 Int64。

  • asFloat, 它返回 float。

  • asDouble, 它返回 double。

  • asBool, 它返回 bool。

此外,这个类还提供了 operator[],让你访问数组元素。

你可以 also 查询一个 Json::Value 对象,使用这些方法之一来确定它的类型:

  • isNull, 如果值是 null 则返回 true。

  • isBool, 如果值是 bool 类型则返回 true。

  • isInt, 如果值是 Int 则返回 true。

  • isUInt, 如果值是 UInt 则返回 true。

  • isIntegral, 如果值是整数则返回 true。

  • isDouble, 如果值是 double 则返回 true。

  • isNumeric, 如果值是数字则返回 true。

  • isString, 如果值是字符串则返回 true。

  • isArray, 如果值是一个数组则返回 true。

  • isObject, 如果值是另一个 JSON 对象(你可以使用另一个 Json::Value 值对其进行分解)则返回 true。

无论如何,我们的代码使用 asString 来获取作为 result 属性的 std::string 值,并将其写入控制台。

代码然后定义了Json::StyledWriter和Json::FastWriter来创建一些格式化的 JSON 和未格式化的 JSON 字符串,以及一个Json::Value对象来包含我们的新 JSON。赋值给 JSON 值很简单,因为它用适当的实现覆盖了operator[]和operator[]=方法,以将标准 C++类型转换为 JSON 对象。因此,以下代码创建了一个带有result属性和ok值的单个 JSON 属性/值对(尽管这段代码没有显示,但你可以通过将 JSON 对象分配给其他 JSON 对象来创建 JSON 属性值树):

newValue["result"] = "ok";

我们首先使用StyledWriter,然后使用FastWriter来编码newValue中的 JSON 值,将每个字符串写入控制台。

当然,你也可以将单个值传递给 JsonCpp;如果你只是想传递一个双精度数,没有理由不执行以下代码。

Json::Reader reader;
Json::Value piValue;

string json = "3.1415";
bool parseSuccess = reader.parse(json, piValue, false);
  double pi = piValue.asDouble();

也请参阅

对于 JsonCpp 的文档,你可以从www.stack.nl/~dimitri/doxygen/安装 doxygen,并将其运行在 JsonCpp 主要分布的doc文件夹上。

还有其他针对 C++的 JSON 转换实现。要查看完整的列表,请参阅json.org/上的列表。

在 C#中读写 JSON

C#是一种常见的客户端语言,用于编写丰富应用程序的客户端实现,以及运行在 ASP.NET 上的 Web 服务的客户端实现。.NET 库在 System.Web.Extensions 程序集中包括 JSON 序列化和反序列化。

准备

这个例子使用了 System.Web.Extensions 程序集中的内置 JSON 序列化和反序列化器,这是许多可用的.NET 库之一。如果你安装了最近版本的 Visual Studio(请参阅www.visualstudio.com/en-us/downloads/visual-studio-2015-downloads-vs.aspx),它应该是可以使用的。要使用这个程序集,你所需要做的就是在 Visual Studio 中通过右键点击你的项目中的引用项,选择添加引用,然后在框架程序集列表中滚动到底部找到System.Web.Extensions。

如何做到...

这是一个简单的应用程序,它反序列化了一些 JSON,作为属性-对象对的字典:

using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;

namespace JSONExample
{
    public class SimpleResult
    {
        public string result;
    }

    class Program
    {
        static void Main(string[] args)
        {
            JavaScriptSerializer serializer = 
            new System.Web.Script.Serialization.
            JavaScriptSerializer();

            string json = @"{ ""call"":""KF6GPE"",""type"":
""l"",""time"":""1399371514"",""lasttime"":""1418597513"",
""lat"": 37.17667,""lng\": -122.14650,""result"": ""ok"" }";

dynamic result = serializer.DeserializeObject(json);
            foreach (KeyValuePair<string, object> entry in result)
            {
                var key = entry.Key;
                var value = entry.Value as string;
Console.WriteLine(String.Format("{0} : {1}", 
key, value));
            }
            Console.WriteLine(serializer.Serialize(result));

            var anotherResult = new SimpleResult { result="ok" };
            Console.WriteLine(serializer.Serialize(
            anotherResult));
        }
    }
}

它是如何工作的...

System.Web.Extensions 程序集提供了System.Web.Script.Serialization名称空间中的JavaScriptSerializer类。这段代码首先定义了一个简单的类SimpleResult,我们将在示例中将其编码为 JSON。

Main方法首先定义了一个JavaScriptSerializer实例,然后定义了一个包含我们 JSON 的string。解析 JSON 只需调用JavaScriptSerializer实例的DeserializeObject方法,该方法根据传递的 JSON 在运行时确定返回对象的类型。

提示

你也可以使用DeserializeObject以类型安全的方式解析 JSON,然后返回对象的类型与传递给方法的类型匹配。我将在第七章使用 JSON 进行类型安全操作中向你展示如何做到这一点。

DeserializeObject返回一个键值对的Dictionary;键是 JSON 中的属性,值是表示这些属性值的对象。在我们示例中,我们简单地遍历字典中的键和值,并打印出来。因为我们知道 JSON 中值的类型,所以我们可以使用 C#的as关键字将其转换为适当的类型(在这个例子中是string);如果不是string,我们将收到null值。你可以使用as或 C#的类型推导来确定 JSON 中未知对象的类型,这使得解析缺乏严格语义的 JSON 变得容易。

JavaScriptSerializer类还包括一个Serialize方法;你可以将其作为属性-值对的字典传递,就像我们对反序列化结果所做的那样,或者你可以将其作为 C#类的实例传递。如果你将其作为类传递,它将尝试通过内省类字段和值来序列化类。

还有更多...

微软提供的 JSON 实现对于许多目的来说已经足够了,但不一定最适合你的应用程序。其他开发者实现了更好的版本,这些版本通常使用与微软实现相同的接口。一个不错的选择是 Newtonsoft 的 Json.NET,你可以从json.codeplex.com/或者从 Visual Studio 的 NuGet 获取。它支持更广泛的.NET 平台(包括 Windows Phone),LINQ 查询,对 JSON 的 XPath-like 查询,并且比微软实现更快。使用它与使用微软实现类似:从 Web 或 NuGet 安装包,将程序集引用添加到你的应用程序中,然后使用NewtonSoft.Json命名空间中的JsonSerializer类。它定义了与微软实现相同的SerializeObject和DeserializeObject方法,使得切换到这个库变得容易。Json.NET的作者James Newton-King将其置于 MIT 许可下。

与其他语言一样,你也可以在反序列化和序列化过程中传递原始类型。例如,在评估以下代码后,动态变量piResult将包含一个浮点数,3.14:

string piJson = "3.14";
dynamic piResult = serializer.DeserializeObject(piJson);

也参见

如我之前所暗示的,你可以以一种类型安全的方式进行操作;我们将在第七章使用 JSON 进行类型安全操作中讨论更多内容。你将通过使用泛型方法DeserializeObject<>,传入你想要反序列化的类型变量来实现。

在 Java 中读写 JSON

Java,像 C++一样,早于 JSON。甲骨文目前正致力于为 Java 添加 JSON 支持,但与此同时,网上有多个提供 JSON 支持的实现。与本章前面看到的 C++实现类似,你可以使用第三方库将 JSON 和 Java 之间进行转换;在这个例子中,作为一个 Java 归档(JAR)文件,其实现通常将 JSON 对象表示为命名的对象的树。

也许最好的 JSON 解析 Java 实现是 Gson,可以从谷歌的code.google.com/p/google-gson/获取,在 Apache 许可证 2.0 下发布。

准备开始

首先,你需要获取 Gson;你可以通过使用以下命令,用 SVN 通过 HTTP 进行只读检出仓库来完成这个操作:

svn checkout http://google-gson.googlecode.com/svn/trunk/google-gson-read-only

当然,这假设你已经安装了一个 Java 开发工具包(www.oracle.com/technetwork/java/javase/downloads/index.html)和 SVN(Windows 上的 TortoiseSVN 是一个好的客户端,可在tortoisesvn.net/downloads.html获得)。许多 Java IDE 包括对 SVN 的支持。

一旦你检查了代码,按照随附的说明构建吉森 JAR 文件,并将 JAR 文件添加到你的项目中。

如何做到…

开始之前,你需要创建一个com.google.gson.Gson对象。这个类定义了你将用来在 JSON 和 Java 之间转换的接口:

Gson gson = new com.google.gson.Gson(); 
String json = "{\"call\": \"KF6GPE\", \"type\": \"l\", \"time\":
\"1399371514\", \"lasttime\": \"1418597513\", \"lat\": 37.17667,
\"lng\": -122.14650,\"result\":\"ok\"}";
com.google.gson.JsonObject result = gson.fromJson(json, 
JsonElement.class).getAsJsonObject(); 

JsonObject类定义了包含 JSON 对象的顶级对象;你使用它的get和add方法来获取和设置属性,像这样:

JsonElement result = result.get("result").getAsString();

吉森库使用JsonElement类来封装单个 JSON 值;它有以下方法,可以让您将JsonElement中的值作为普通的 Java 类型获取:

  • getAsBoolean,返回值为Boolean

  • getAsByte,返回值为byte

  • getAsCharacter,返回值为char

  • getAsDouble,返回值为double

  • getAsFloat,返回值为float

  • getAsInt,返回值为int

  • getAsJsonArray,返回值为JsonArray

  • getAsJsonObject,返回值为JsonObject

  • getAsLong,返回值为long

  • getAsShort,返回值为short

  • getAsString,返回值为String

你也可以使用以下方法之一了解JsonElement中的类型:

  • isJsonArray,如果元素是一个对象数组则返回true

  • isJsonNull,如果元素为 null 则返回true

  • isJsonObject,如果元素是一个复合对象(另一个 JSON 树)而不是单个类型则返回true

  • isJsonPrimitive,如果元素是基本类型,如数字或字符串,则返回true

还有更多…

你也可以直接将类的实例转换为 JSON,像这样编写代码:

public class SimpleResult {
    public String result;
}

// Elsewhere in your code…
Gson gson = new com.google.gson.Gson(); 
SimpleResult result = new SimpleResult;
result.result = "ok";
String json = gson.toJson(result);	

这定义了一个SimpleResult类,我们用它来创建一个实例,然后使用Gson对象实例将转换为包含 JSON 的字符串,使用Gson方法的toJson。

最后,因为JsonElement封装了一个单独的值,你也可以处理表示为 JSON 的简单值,比如这样:

Gson gson = new com.google.gson.Gson(); 
String piJson = "3.14";
double result = gson.fromJson(piJson, 
JsonElement.class).getAsDouble(); 

这会将 JSON 中的原始值3.14转换为 Java double。

也见

与 C#示例类似,你可以直接从 JSON 转换为普通的旧 Java 对象(POJO),并以类型安全的方式进行转换。你将在第七章 以类型安全的方式使用 JSON中看到如何做到这一点。

还有其他针对 Java 的 JSON 转换实现。要获取完整列表,请查看json.org/上的列表。

在 Perl 中读写 JSON。

Perl 早于 JSON,尽管 CPAN 有一个很好的 JSON 转换实现,即综合 Perl 存档网络(Comprehensive Perl Archive Network)。

如何做到...

首先,从 CPAN 下载 JSON 模块并安装它。通常,你会下载文件,解压它,然后在已经配置了 Perl 和 make 的系统上运行以下代码:

perl Makefile.PL 
make 
make install

这是一个简单示例:

use JSON;
use Data::Dumper;
my $json = '{ "call":"KF6GPE","type":"l","time":"1399371514",
"lasttime":"1418597513","lat": 37.17667,"lng": -122.14650,
"result" : "ok" }';
my %result = decode_json($json);
print Dumper(result);
print encode_json(%result);

让我们来看看 JSON 模块提供的接口。

它是如何工作的...

CPAN 模块定义了decode_json和encode_json方法来分别解码和编码 JSON。这些方法在 Perl 对象(如字面值和关联数组)和包含 JSON 的字符串之间进行相互转换。

代码首先导入了 JSON 和Data::Dumper模块。接下来,它定义了一个单一字符串$json,其中包含我们要解析的 JSON。

有了 JSON 中的$json,我们定义%result为包含 JSON 中定义的对象的关联数组,并在下一行倾倒散列中的值。

最后,我们将散列重新编码为 JSON,并将结果输出到终端。

也见

要获取更多信息并下载 JSON CPAN 模块,请访问metacpan.org/pod/JSON。

在 Python 中读写 JSON。

从 Python 2.6 开始,Python 就拥有对 JSON 的本地支持,通过json模块。使用该模块就像使用import语句导入模块一样简单,然后通过它定义的json对象访问编码器和解码器。

准备好了

只需在源代码中输入以下内容,就可以引用 JSON 功能:

import json

如何做到...

以下是从 Python 解释器中的一个简单示例:

>>> import json
>>>json = '{ "call":"KF6GPE","type":"l","time":"1399371514",
"lasttime":"1418597513","lat": 37.17667,"lng": -122.14650,
"result" : "ok" }'
u'{"call":"KF6GPE","type":"l","time":"1399371514",
"lasttime":"1418597513","lat": 37.17667,"lng": -122.14650,
"result": "ok" }'
>>>result = json.loads(json)
{u'call':u'KF6GPE',u'type':u'l',u'time':u'1399371514',
u'lasttime':u'1418597513',u'lat': 37.17667,u'lng': -122.14650,u'result': u'ok'}
>>> result['result']
u'ok'
>>> print json.dumps(result)
{"call":"KF6GPE","type":"l","time":"1399371514",
"lasttime":"1418597513","lat": 37.17667,"lng": -122.14650,
"result":"ok"}
>>> print json.dumps(result, 
...                  indent=4)
{
"call":"KF6GPE",
"type":"l",
"time":"1399371514",
"lasttime":"1418597513",
"lat": 37.17667,
"lng": -122.14650,
    "result": "ok"
}

让我们更深入地看看loads和dumps。

它是如何工作的...

Python 语言通过其对象层次结构对关联数组提供了很好的支持。json 模块提供了一个 json 对象以及 loads 和 dumps 方法,这些方法可将文本字符串中的 JSON 转换为关联数组,反之亦然。如果你熟悉 Python 的 marshal 和 pickle 模块,这个接口是相似的;你使用 loads 方法从其 JSON 表示中获取 Python 对象,使用 dumps 方法将一个对象转换为其 JSON 等价物。

之前的列表正是这样做。它定义了一个变量 j 来包含我们的 JSON 数据,然后使用 json.loads 获得一个 Python 对象 result。JSON 中的字段作为命名的对象在生成的 Python 对象中是可访问的。(注意我们不能将我们的 JSON 字符串命名为 json,因为这会遮蔽模块接口的定义。)

要转换为 JSON,我们使用 json.dumps 方法。默认情况下,dumps 创建一个紧凑的、机器可读的 JSON 版本,最小化空白;这最适合用于网络传输或文件存储。当你在调试你的 JSON 时,使用缩进和分隔符周围的一些空白来美化打印它是有帮助的;你可以使用可选的 indent 和 separators 参数来实现。indent 参数指定了每个嵌套对象在字符串中应缩进的空格数,而 separators 参数指定了每个对象之间以及每个属性和值之间的分隔符。

另见

关于 json 模块的更多文档,请参阅 Python 文档中的 docs.python.org/2/library/json.html。

第二章:服务器端的读写 JSON

在前一章中,我们查看了一些最常见的客户端环境中的 JSON 处理。在本章中,我们将注意力转向服务器端的 JSON 编码和解码。我们将查看以下环境中的食谱:

  • 在 Clojure 中读写 JSON

  • 在 F#中读写 JSON

  • 在 Node.js 中读写 JSON

  • 在 PHP 中读写 JSON

  • 在 Ruby 中读写 JSON

一些语言,如 C++和 Java,既用于客户端又用于服务器端;对于这些语言,请参考第一章,客户端的读写 JSON(一个例外是关于 Node.js 中的 JSON 讨论,因为 Node.js 在这本书的后续章节中扮演重要角色)。

在 Clojure 中读写 JSON

Clojure 是一种运行在 Java 和 Microsoft 公共 语言运行时(CLR)平台之上的现代 Lisp 变体。因此,你可以使用我们在第一章中讨论的设施,在本地运行时将 JSON 和对象之间进行转换,但有一个更好的方法,那就是 Clojure 的data.json模块,可在github.com/clojure/data.json找到。

准备

首先,你需要在你data.json模块中指定你的依赖。你可以用以下依赖在你的 Leiningen 文件中这样做:

[org.clojure/data.json "0.2.5"]

如果你使用 Maven,你会需要这个:

<dependency>
<groupId>org.clojure</groupId>
<artifactId>data.json</artifactId>
<version>0.2.5</version>
</dependency>

提示

当然,data.json的版本可能会在我写这篇和你在项目中作为依赖包含它之间发生变化。查看 data.json 项目以获取当前版本。

最后,你需要在你的代码中包含data.json模块,在一个像json这样的命名空间中:

(ns example
  (:require [clojure.data.json :as json])

这使得data.json模块的实现通过json命名空间可用。

如何做到...

将 Clojure 映射编码为 JSON 很容易,只需调用json/write-str。例如:

(json/write-str {:call "KF6GPE",:type "l",:time 
"1399371514":lasttime"1418597513",:lat 37.17667,:lng
-122.14650: :result "ok"})
;;=>"{\"call\": \"KF6GPE\", \"type\": \"l\", \"time\":
\"1399371514\", \"lasttime\": \"1418597513\", \"lat\": 37.17667,
\"lng\": -122.14650,\"result\":\"ok\"}"

如果你有一个实现java.io.Writer的流,你想向其写入 JSON,你也可以使用json/write:

(json/write {:call "KF6GPE",:type "l", :time 
"1399371514":lasttime "1418597513",:lat 37.17667, :lng 
-122.14650: result "ok" }  stream)

阅读是写作的相反过程,它将 JSON 读取到关联数组中,你可以进一步处理:

(json/read-str "{\"result\":\"ok\"}")
;;=> {"result" "ok"}

还有json/read,json/write的对应物,它接收一个流,你可以从中读取并返回解析后的 JSON 映射。

还有更多...

这些方法都接受两个可选参数,一个:key-fn参数,模块将其应用于每个 JSON 属性名称,和一个:value-fn参数,模块将其应用于属性值。例如,你可以使用:key-fn keyword将 JSON 转换为更传统的 Clojure 关键词映射,像这样:

(json/read-str "{\"call\": \"KF6GPE\", \"type\": \"l\", \"time\":
\"1399371514\", \"lasttime\": \"1418597513\", \"lat\": 37.17667,
\"lng\": -122.14650,\"result\":\"ok\"}:key-fn keyword)
;;=> {:call "KF6GPE",:type "l", :time 
"1399371514":lasttime "1418597513",:lat 37.17667, :lng 
-122.14650: :result "ok"}

或者,你可以提供一个 lambda,比如以下这个,它将键转换为大写:

(json/write-str {:result "OK"}
                :key-fn #(.toUpperCase %))
;;=> "{\"RESULT\":"OK"}"

这里有一个来自 data.json 文档的很好例子,它使用 value-fn 将 ISO 日期字符串转换为 Java Date 对象,当你解析 JSON 时:

(defn my-value-reader [key value]
  (if (= key :date)
    (java.sql.Date/valueOf value)
    value))

(json/read-str "{\"result\":\"OK\",\"date\":\"2012-06-02\"}"
               :value-fn my-value-reader
               :key-fn keyword) 
;;=> {:result"OK", :date #inst "2012-06-02T04:00:00.000-00:00"}

上述代码执行以下操作:

  1. 定义了一个辅助函数 my-value-reader,它使用 JSON 键值对的关键字来确定其类型。

  2. 给定一个 JSON 键值 :date,它将该值视为一个字符串,传递给 java.sql.Date 方法的 valueOf,该方法返回一个从它解析的字符串中获取值的 Date 实例。

  3. 调用 json/read-str 来解析一些简单的 JSON 数据,它包括两个字段:一个 result 字段和一个 date 字段。

  4. JSON 解析器解析 JSON 数据,将 JSON 属性名转换为关键字,并使用我们之前定义的值转换器将日期值转换为它们的 java.sql.Date 表示形式。

在 F# 中读写 JSON

F# 是一种运行在 CLR 和 .NET 之上的语言,擅长于函数式和面向对象编程任务。因为它建立在 .NET 之上,所以你可以使用诸如 Json.NET(在 第一章,客户端的 JSON 读写 中提到)等第三方库来将 JSON 与 CLR 对象进行转换。然而,还有一个更好的方法:开源库 F# Data,它创建了本地的数据类型提供程序来处理多种不同的结构化格式,包括 JSON。

准备开始

首先,获取库的副本,可在 github.com/fsharp/FSharp.Data 找到。下载后,你需要构建它;你可以通过运行随分发提供的 build.cmd 构建批处理文件来完成此操作(有关详细信息,请参见 F# Data 网站)。或者,你可以在 NuGet 上找到相同的包,通过从 项目 菜单中选择 管理 NuGet 包 并搜索 F# Data。找到后,点击 安装。我更喜欢使用 NuGet,因为它会自动将 FSharp.Data 程序集添加到你的项目中,省去了你自己构建源代码的麻烦。另一方面,源分发可以让你离线阅读文档,这也很有用。

一旦你获得了 F# 数据,你只需要在源文件中打开它,你打算用它时使用 open 指令,像这样:

open FSharp.Data

如何进行...

以下是一些示例代码,它实现了 JSON 与 F# 对象之间的转换,然后又从另一个 F# 对象创建了新的 JSON 数据:

open FSharp.Data

type Json = JsonProvider<""" { "result":"" } """>
let result = Json.Parse(""" { "result":"OK" } """)
let newJson = Json.Root( result = "FAIL")

[<EntryPoint>]
let main argv = 
    printfn "%A" result.Result
    printfn "%A" newJson
    printfn "Done"

让我们看看它是如何工作的。

它是如何工作的...

首先,重要的是要记住 F#是强类型的,并且从数据中推断类型。理解这一点对于理解 F# Data 库是如何工作的至关重要。与我们在前面章节中看到的例子不同,在那里转换器将 JSON 映射到键值对,F# Data 库从您提供的 JSON 中推断出整个数据类型。在许多方面,这既结合了其他转换器在转换 JSON 时采取的动态集合导向方法,也结合了我在第七章 以类型安全的方式使用 JSON中要展示的类型安全方法。这是因为您不需要辛苦地制作要解析的 JSON 的类表示,而且您还能在编写的代码中获得编译时类型安全的所有优势。更妙的是,F# Data 创建的类都支持 Intellisense,所以您在编辑器中就能直接获得工具提示和名称补全!

让我们逐段看看之前的示例并了解它做了什么:

open FSharp.Data

第一行使 F# Data 类可用于您的程序。 among other things, 这定义了JsonProvider类,该类从 JSON 源创建 F#类型:

type Json= JsonProvider<""" { "result":"" } """>

这行代码定义了一个新的 F#类型Json,该类型的字段和字段类型是从您提供的 JSON 中推断出来的。在底层,这做了很多工作:它推断出成员名称、成员的类型,甚至处理诸如混合数值(比如说您有一个同时包含整数和浮点数的数组,它能正确推断出类型为数值类型,这样你就可以表示任一类型)以及复杂的记录和可选字段等事情。

您可以向JsonProvider传递以下三个之一:

  1. 一个包含 JSON 的字符串。这是最简单的情况。

  2. 一个包含 JSON 文件的路径。库将打开文件并读取内容,然后对内容进行类型推断,最后返回一个能够表示文件中 JSON 的类型。

  3. 一个 URL。库将获取 URL 处的文档,解析 JSON,然后对内容进行相同的类型推断,返回一个代表 URL 处 JSON 的类型。

下一行解析一个单独的 JSON 文档,如下所示:

let result = Json.Parse(""" { "result":"OK" } """)

这可能一开始看起来有点奇怪:为什么我们要把 JSON 同时传递给JsonProvider和Parse方法呢?回想一下,JsonProvider是从您提供的 JSON 中创建一个类型。换句话说,它不是为了解析 JSON 的值,而是为了解析它所表示的数据类型,以便制作一个能够模拟 JSON 文档本身的类。这一点非常重要;对于JsonProvider来说,您想要传递一个具有字段和值的代表性 JSON 文档,这个文档是您的应用程序可能遇到的某一特定类型的所有 JSON 文档的共通之处。然后,您将一个特定的 JSON 文档(比如说,一个 web 服务结果)传递给JsonProvider创建的类的Parse方法。相应地,Parse返回了一个您调用Parse的方法的实例。

现在你可以访问类Parse返回实例中的字段;例如,稍后,我将在应用程序的main函数中打印出result.Result的值。

创建 JSON,你需要一个表示要序列化数据的类型的实例。在下一行,我们使用刚刚创建的Json类型来创建一个新的 JSON 字符串:

let newJson = Json.Root( result = "FAIL")

这创建了一个Json类型的实例,并将结果字段设置为字符串FAIL,然后将该实例序列化为一个新的字符串。

最后,程序的其余部分是程序的入口点,并只是打印出解析的对象和创建的 JSON。

还有更多...

F#数据库支持远不止只是 JSON;它还支持逗号分隔值(CSV)、HTML 和 XML。这是一个非常适合进行各种结构化数据访问的优秀库,如果你在 F#中工作,那么熟悉它绝对是件好事。

使用 Node.js 读写 JSON

Node.js 是一个基于与 Google 为 Chrome 构建的高性能 JavaScript 运行时相同的高性能和异步编程模型的 JavaScript 环境,由 Joyent 支持。它高性能和异步编程模型使它成为一个优秀的自定义 Web 服务器环境,并且它被包括沃尔玛在内的许多大型公司用于生产环境。

准备

因为我们在接下来的两章中也会使用 Node.js,所以值得向你指出如何下载和安装它,即使你的日常服务器环境更像 Apache 或 Microsoft IIS。你需要访问www.nodejs.org/,并从首页下载安装程序。这将安装运行 Node.js 和 npm(Node.js 使用的包管理器)所需的一切。

提示

在 Windows 上安装后,我必须重新启动计算机,才能使 Windows 外壳正确找到 Node.js 安装程序安装的 node 和 npm 命令。

一旦你安装了 Node.js,我们可以通过在 Node.js 中启动一个简单的 HTTP 服务器来测试安装。为此,将以下代码放入一个名为example.js的文件中:

var http = require('http');
http.createServer(function(req, res) {
   res.writeHead(200, {'Content-Type': 'text/plain'});
   res.end('Hello world\n');
}).listen(1337, 'localhost');
console.log('Server running at http://localhost:1337');

这段代码加载了 Node.js 的http模块,然后创建了一个绑定在本地机器上端口1337的 Web 服务器。你可以在创建文件的同一目录下,通过命令提示符输入以下命令来运行它:

node example.js

一旦这样做,将你的浏览器指向 URLhttp://localhost:1337/。如果一切顺利,你应该在网页浏览器中看到“Hello world”的消息。

提示

你可能需要告诉你的系统防火墙,以启用对由node命令服务的端口的访问。

怎么做...

由于 Node.js 使用 Chrome 的 V8 JavaScript 引擎,因此 Node.js 中处理 JSON 与 Chrome 中的处理方式相同。JavaScript 运行时定义了JSON对象,该对象为您提供了 JSON 解析器和序列化器。

解析 JSON,你所需要做的就是调用JSON.parse方法,像这样:

var json = '{ "call":"KF6GPE","type":"l","time":
"1399371514","lasttime":"1418597513","lat": 37.17667,"lng":
-122.14650,"result" : "ok" }';
var object = JSON.parse(json);

这将解析 JSON,返回包含数据的 JavaScript 对象,我们在这里将其分配给变量 object。

当然,你可以做相反的操作,使用JSON.stringify,像这样:

var object = { 
call:"KF6GPE",
type:"l",
time:"1399371514",
lasttime:"1418597513",
lat:37.17667,
lng:-122.14650,
result: "ok"
};

var json = JSON.stringify(object);

也见

关于在 JavaScript 中解析和创建 JSON 的更多信息,请参阅第一章中的在客户端读写 JSON一节,以及读写客户端 JSON。

在 PHP 中读写 JSON

PHP 是一个流行的服务器端脚本环境,可以轻松与 Apache 和 Microsoft IIS 网络服务器集成。它具有内置的简单 JSON 编码和解码支持。

如何做到...

PHP 提供了两个函数json_encode和json_decode,分别用于编码和解码 JSON。

你可以将原始类型或自定义类传递给json_encode,它将返回一个包含对象 JSON 表示的字符串。例如:

$result = array(
"call" =>"KF6GPE",
"type" =>"l",
"time" =>"1399371514",
"lasttime" =>"1418597513",
"lat" =>37.17667,
"lng" =>-122.14650,
"result" =>"ok");
$json = json_encode($result);

这将创建一个包含我们关联数组的 JSON 表示的字符串$json。

json_encode函数接受一个可选的第二个参数,让你指定编码器的参数。这些参数是标志,所以你可以用二进制或|操作符来组合它们。你可以传递以下标志的组合:

  • JSON_FORCE_OBJECT:这个标志强制编码器将 JSON 编码为对象。

  • JSON_NUMERIC_CHECK:这个标志检查传入结构中的每个字符串的内容,如果它包含一个数字,则在编码之前将字符串转换为数字。

  • JSON_PRETTY_PRINT:这个标志将 JSON 格式化为更容易供人类阅读的形式(不要在生产环境中这样做,因为这会使 JSON 变大)。

  • JSON_UNESCAPED_SLASHES:这个标志指示编码器不要转义斜杠字符。

最后,你可以传递一个第三个参数,指定编码器在编码你传递的值时应遍历表达式的深度。

json_encode的补数是json_decode,它接受要解码的 JSON 和一个可选参数集合。其最简单的用法可能像这样:

$json = '{ "call":"KF6GPE","type":"l","time":
"1399371514","lasttime":"1418597513","lat": 37.17667,"lng":
-122.14650,"result" : "ok" }';
$result = json_decode($json);

json_decode函数最多接受三个可选参数:

  • 第一个参数,当为真时,指定结果应该以关联数组的形式返回,而不是stdClass对象。

  • 第二个参数指定一个可选的递归深度,以确定解析器应深入解析 JSON 的深度。

  • 第三个参数可能是选项JSON_BIGINT_AS_STRING,当设置时表示应该将溢出整数值的整数作为字符串返回,而不是转换为浮点数(这可能会失去精度)。

这些函数在成功时返回true,在出错时返回false;你可以通过检查json_last_error的返回值来使用 JSON 确定上一次错误的的原因。

在 Ruby 中读写 JSON

Ruby 提供了json宝石用于处理 JSON。在 Ruby 的早期版本中,你必须自己安装这个宝石;从 Ruby 1.9.2 及以后版本开始,它成为基础安装的一部分。

准备

如果你正在使用比 Ruby 1.9.2 更早的版本,首先需要使用以下命令安装 gem:

gem install json

请注意,Ruby 的实现是 C 语言,因此安装 gem 可能需要 C 编译器。如果您系统上没有安装,您可以使用以下命令安装 gem 的纯 Ruby 实现:

gem install json_pure

无论你是否需要安装 gem,你都需要在代码中包含它。要做到这一点,请包含rubygems和json或json/pure,具体取决于你安装了哪个 gem;使用require,像这样:

require 'rubygems'
require 'json'

前面的代码处理了前一种情况,而下面的代码处理了后一种情况:

require 'rubygems'
require 'json/pure'

如何做到...

该 gem 定义了 JSON 对象,其中包含parse和generate方法,分别用于序列化和反序列化 JSON。使用它们正如你所期望的那样。创建一个对象或一些 JSON,调用相应的函数,然后查看结果。例如,要使用 JSON.generate 创建一些 JSON,你可以执行以下操作:

require 'rubygems'
require 'json'
object = { 
"call" =>"KF6GPE",
"type" =>"l",
"time" =>"1399371514",
"lasttime" =>"1418597513",
"lat" => 37.17667,
"lng" => -122.14650,
"result" =>"ok"
}
json = JSON.generate(object)

这包括必要的模块,创建一个具有单个字段的关联数组,然后将其序列化为 JSON。

反序列化工作方式与序列化相同:

require 'rubygems'
require 'json'
json = '{ "call":"KF6GPE","type":"l","time":
"1399371514","lasttime":"1418597513","lat": 37.17667,"lng":
-122.14650,"result" : "ok" }'
object = JSON.parse(object)

parse函数可以接受一个可选的第二个参数,一个具有以下键的哈希,表示解析器的选项:

  • max_nesting表示允许在解析的数据结构中嵌套的最大深度。它默认为 19,或者可以通过传递:max_nesting => false来禁用嵌套深度检查。

  • allow_nan,如果设置为真,则允许 NaN、Infinity 和-Infinity,这与 RFC 4627 相悖。

  • symbolize_names,当为真时,返回 JSON 对象中属性名的符号;否则,返回字符串(字符串是默认值)。

另见

JSON Ruby gem 的文档可以在网上找到,网址为flori.github.io/json/doc/index.html。

第三章:使用 JSON 的简单 AJAX 应用程序

在本章中,我们将探讨 JSON 在提供比旧网页更好的响应性的异步 JavaScript 和 XML(AJAX)应用程序中所扮演的角色,这些应用程序通过动态按需加载网页的片段来实现。

在本章中,您将找到以下食谱:

  • 创建XMLHttpRequest对象

  • 为数据发起异步请求

  • 将 JSON 发送到你的 Web 服务器

  • 使用 Node.js 接受 JSON

  • 获取异步请求的进度

  • 解析返回的 JSON

  • 使用 Node.js 发起 Web 服务请求

引言

AJAX 是一组用于网络开发的客户端技术,用于创建异步网络应用程序——能够从不同的服务器获取内容的网络页面,一旦加载了基本内容。AJAX 中的“X”代表 XML,但今天的 AJAX 应用程序通常使用 JSON 来封装客户端和服务器之间的数据。

AJAX 的基础组件实际上 quite old,可以追溯到 1998 年由 Microsoft 在 Internet Explorer 中引入的 ActiveX 组件。

然而,这项技术实际上在 2005 年得到了广泛的应用,当时杰西·加勒特(Jesse Garrett)撰写了一篇题为《Ajax:一种新的网络应用程序方法》的文章。2006 年 4 月,万维网联盟发布了XMLHttpRequest对象的第一个草案标准,这是当今所有现代浏览器中所有 AJAX 应用程序的底层对象。

在本章中,我们将构建一个简单的 AJAX 应用程序,该程序通过自动数据包报告系统(APRS)网络返回一个业余无线电台报告的纬度和经度,这些数据由www.aprs.fi/网站进行缓存,这是一个在业余无线电台社区中广受欢迎的网站。我们将使用 HTML 和 JavaScript 为 Google Chrome 和 Internet Explorer 构建客户端,并使用 Node.js 构建服务器端。

提示

首先,请确保你按照第二章,在服务器上读写 JSON,中的使用 Node.js 读写 JSON部分安装了 Node.js。你还需要安装 Node.js 的 request 模块。在安装 Node.js 后,通过命令提示符运行npm install request来实现。

设置服务器

我们将从一个骨架服务器开始。为你的 node 应用程序创建一个目录,并将以下内容保存到json-encoder.js中:

var http = require('http');
var fs = require('fs');
var url = require('url');

http.createServer(function(req, res) {
if (req.method == 'POST') {
  console.log('POST');
  var body = '';
  req.on('data', function(data) {
    body += data;
  });
  req.on('end', function() {     
    res.writeHead(200, 
     {'Content-Type': 'application/json'});
    res.end("null");
    });
  } 
  elseif (req.method == 'GET')
  {
    console.log('GET');
    var urlParts = url.parse(req.url);
    if (urlParts.pathname == "/favicon.ico")
    {
      res.end("");
      return;
    }

    res.writeHead(200, {'Content-Type': 'text/plain'});

    var html = fs.readFileSync('./public' + urlParts.pathname);
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(html); 
    return;    
  }
}).listen(1337, 'localhost');
console.log('Server running at http://127.0.0.1:1337');

这段代码处理两种 HTTP 请求:POST 请求和 GET 请求。它首先分配了 http、filesystem 和 url 操作对象,然后在本地的 1337 端口上注册了一个 HTTP 服务器。它的服务器根据请求类型进行切换。对于 POST 请求,它目前返回一个空的 JSON 体,忽略其传入的内容。对于 GET 请求,它尝试从当前工作目录下面的 public 子目录中加载 URL 指示的文件,并将其作为 HTML 文档返回给客户端。如果传入的请求是针对 favicon 的,它将忽略该请求。

这个服务器很原始但足以满足我们的需求。如果你对学习更多关于 Node.js 的内容感兴趣,你可能会想为以下目的扩展它:

  • 正确确定返回文档的 MIME 类型,并根据文档的 MIME 类型发送适当的 Content-Type 头部。

  • 如果找不到给定的文档,不要抛出异常并杀死服务器,而是返回一个 404 页面未找到错误。

我们将在本章中扩展服务器端的 JavaScript。

设置客户端页面

在 json-encoder.js 中创建一个子目录,并将其命名为 public。在这个目录中,创建一个包含以下 HTML 的 HTML 文件,并将其命名为 json-example.html:

<!DOCTYPE html>
<html>
<head>

</head>
<body onload="doAjax()">

<p>Hello world</p>
<p>
<div id="debug"></div>
</p>
<p>
<div id="json"></div>
</p>
<p>
<div id="result"></div>
</p>

<p>Powered by <a href="http://www.aprs.fi">aprs.fi</a></p>

<script type="text/javascript">
var debug = document.getElementById('debug');

function doAjax() {
  document.getElementById("result").innerHTML = 
    "loaded... executing.";
}
</script>
</body>
</html>

这是一个包含三个 div 标签的简单 HTML 文档,我们将从异步请求中填充这些标签的数据:debug 用于显示调试信息;json 用于显示原始 JSON;result 用于显示实际结果,这将显示从解析 JSON 的 JavaScript 对象中获取的格式化数据。页面底部有一个脚本 doAjax,浏览器在加载所有 HTML 后通过 body 标签的 onload 属性调用它。

在 Chrome 中使用开发者工具激活加载网页,你应该看到类似这样的内容:

设置客户端页面

我们将在本章中扩展 HTML。

创建 XMLHttpRequest 对象

所有现代网络浏览器都提供了一个 XMLHttpRequest 类,你可以在代码中实例化它,你可以使用它发出异步调用以通过 HTTP 获取内容。你将在客户端的 JavaScript 中使用 new 操作符创建一个或多个这样的实例。

如何进行...

你希望在 JavaScript 页面加载后尽早创建这个类的实例,如下面的代码所示:

function doAjax() {
var xmlhttp;
if (window.XMLHttpRequest)
  {
    // code for IE7+, Firefox, Chrome, Opera, Safari
    xmlhttp=new XMLHttpRequest();
  }
}

它是如何工作的…

上述代码测试了根级别的 JavaScript window 对象是否具有 XMLHttpRequest 类,如果浏览器定义了该类,则为我们创建了该类的实例,以便在制作异步请求时使用。

参见

如果你正在使用一个非常旧的 Internet Explorer 版本,你可能需要使用一个 Microsoft.XMLHTTP ActiveX 对象。在这种情况下,window.XMLHttpRequest 的测试将失败。

制作数据异步请求

您使用创建的XMLHttpRequest类的实例来请求数据。您可以使用任何 HTTP 方法来请求数据;通常您会使用 GET 或 POST。GET 很好,如果您不需要传递任何参数,或者如果参数已编码在服务 URL 中;POST 是必要的,如果您需要将 JSON 作为服务器端脚本的参数提交给服务器。

如何做到...

继续增强我们的客户端页面脚本doAjax函数,以下是如何发起异步请求,修改之前的示例:

function doAjax() {
  var xmlhttp;
  if (window.XMLHttpRequest)
  {
    // code for IE7+, Firefox, Chrome, Opera, Safari
    xmlhttp=newXMLHttpRequest();

    xmlhttp.open("POST","/", true);
    xmlhttp.send("");
  }
}

它是如何工作的…

XMLHttpRequest类有两个用于发起请求的方法:open和send。您使用open方法来开始发出请求的过程,如果需要发送数据(例如,与POST请求一起)供服务器处理,则使用send方法。

open方法接受三个参数:HTTP 方法、URL(相对于包含脚本的页面)和一个布尔值,指示请求是否应为同步(由值false表示)或异步(由值true表示)。在前面的代码中,我们向 web 服务器的根提交了一个POST请求,并请求浏览器以异步方式处理请求,因此页面将被渲染,用户可以与页面交互。

send方法接受一个参数,一个包含您希望发送给服务器的数据的字符串。在这个例子中,我们不发送任何东西;我们将使用这个方法来发送我们的参数的 JSON。

也见

这个菜谱与下一个菜谱向你的 web 服务器发送 JSON非常相关,我们在其中实际上创建了一个 JavaScript 对象,将其字符串化,并使用send方法发送它。

向你的 web 服务器发送 JSON

有些 AJAX 请求只需要从 URL 获取数据。这种情况下,服务器为所有客户端更新一个对象,或者当一个对象的 URL 唯一地标识该对象时(在设计使用代表性状态转移(REST)的服务时很常见)。其他时候,您可能希望将 JavaScript 数据传递给服务器,例如当您有一个复杂的查询需要服务器处理时。为此,创建您的 JavaScript 对象,然后将其字符串化,并将包含 JSON 的字符串传递给XMLHttpRequest对象的send方法。

如何做到...

省略创建XMLHttpRequest对象的代码,您使用以下代码向服务器发送 JSON:

function doAjax() {
  // … create XMLHTTPObject as before

    var request = { 
    call: "kf6gpe-7"
  };

xmlhttp.open("POST","/", true);
xmlhttp.setRequestHeader("Content-Type","application/json");
xmlhttp.send(JSON.stringify(request));
}

请注意,我们这里使用了一个 HTTP POST请求,它将 JSON 文档作为 HTTP 对象主体提交给服务器。

它是如何工作的…

这段代码创建了一个具有单个字段:call 的 JavaScript 对象请求。call 字段的值设置为我们寻找的车站,服务器在处理请求时会使用它。

当你向服务器传递数据时,你应该正确设置 Content-Type 头,HTTP 使用这个头来指示服务器正在传输的数据类型。JSON 的 MIME 类型是 application/json;然而,一些网络应用程序开发者选择了其他表示形式,如text/x-json、text/x-javascript、text/javascript或application/x-javascript。除非你有充分的理由(想想服务器上无法修复的遗留代码),否则你应该使用application/json。你通过使用setRequestHeader方法设置一个请求头来自定义内容类型。这个方法有两个参数:要设置的头的名称及其值。请注意,头名称是大小写敏感的!

一旦设置了请求头,最后要做的就是调用send并传递字符串化的 JavaScript 对象。我们在前面的示例的最后一行这样做。

使用 Node.js 接受 JSON

不同的网络服务器系统以不同的方式接受客户端提交的数据。话说回来,在大多数情况下,你按片读取来自客户端的数据,一旦 POST 请求完成,就将其作为一批数据处理。以下是使用 Node.js 进行处理的方法。

如何做到这一点...

在我们的案例中,我们通过 HTTP POST请求接受客户端提交的 JSON。为此,我们需要从客户端读取数据,将其汇总成字符串,当所有数据到达服务器时,将数据从 JSON 字符串转换为 JavaScript 对象。在 json-encoder.js 中,我们将其修改为如下所示:

 // … beginning of script is the same as in the introduction
    if (req.method == 'POST') {
 console.log('POST');
 var body = '';
 req.on('data', function(data) {
 body += data;
 });
 req.on('end', function() { 
 var json = JSON.parse(body);
 json.result = 'OK';
 res.writeHead(200, 
 {'Content-Type': 'application/json'});

 res.end(JSON.stringify(json));
 });
  }
  // and script continues with the GET if statement and code

它是如何工作的…

前面的代码扩展了本章介绍中的服务器端 Node.js 脚本。这段代码首先检查POST请求方法。如果我们收到一个POST请求,我们创建一个空字符串body来包含请求的主体。Node.js 是事件驱动的;为了从POST请求中读取数据,我们向请求添加了一个'data'事件处理程序,该处理程序将新读取的数据连接到变量body所引用的值。

在某个时刻,POST请求结束,这导致请求引发'end'事件。我们为这个事件注册一个事件处理程序,该处理程序使用JSON.parse解析传入的 JSON。然后,我们在结果对象中设置一个额外的字段,即结果字段,并将其值设为' OK'。最后,我们使用writeHead和end方法分别设置内容类型头和向客户端写入代表该对象的 JSON。

也请参阅

如引言中所建议,你如何在服务器上读取已提交的数据很大程度上取决于服务器环境和服务器端脚本语言。如果你以前没做过这件事,去一个搜索引擎,比如 Bing 或 Google,是很有必要的。一旦你这样做,准备好取出的字符串数据,并在你的服务器端脚本语言中使用其中一个食谱将其转换为对象,这个食谱来自第二章,阅读和编写服务器端的 JSON。

获取异步请求的进度

我们的请求相当轻量级,但这种情况在你的应用程序中并不总是如此。此外,在移动网络应用程序中,特别是在移动设备可能进入和退出网络覆盖并遭受暂时性网络中断时,进度的监控尤为重要。一个健壮的应用程序将测试进度状态和错误,并重试重要的请求。

XMLHttpRequest对象提供了事件,用于通知你有关待处理请求的进度。这些事件如下:

  • load: 此事件在你打开一个连接后立即执行。

  • loadstart: 此事件在加载开始时执行。

  • progress: 此事件在加载过程中定期执行。

  • error: 在发生网络错误的情况下执行此事件。

  • abort: 在网络交易被取消的情况下执行此事件(例如,用户导航离开发出请求的页面)。

如何实现...

对于这些事件中的每一个,你都希望注册一个以某种方式处理事件的函数。例如,error处理程序应该通知用户发生了错误,而abort处理程序应该在请求被放弃的情况下清理任何客户端数据。

以下是一个如何实现此功能的示例,它报告了这些事件的调试信息;这将是我们的示例 HTML 文件底部<script>标签中的内容:

// Add the following functions to the script in the HTML…
function progress(evt){
  debug.innerHTML += "'progress' called...<...<br/>";/>";
}

function abort(evt){
  debug.innerHTML += "'abort' called...<br />";
}

function error(evt){
  debug.innerHTML += "'error' called...<br />";
}

function load(evt){
  debug.innerHTML += "'load' called...<br />";
}

function loadstart(evt){
  debug.innerHTML += "'loadstart' called<br />;
}

function doAjax() {
  // create xmlhttp object as usual

  var request = { 
    call: "kf6gpe-7"
  };

 xmlhttp.addEventListener("loadstart", loadstart, false);
 xmlhttp.addEventListener("progress", progress, false); 
 xmlhttp.addEventListener("load", load, false);
 xmlhttp.addEventListener("abort", abort, false);
 xmlhttp.addEventListener("error", error, false);

  // issue request in the usual way…
}

如何工作...

XMLHttpRequest对象提供了addEventListener方法,你可以用它来注册对象在特定事件发生时应该调用的函数。向这个方法传递事件名称、要在事件上执行的函数(或闭包)以及是否应该捕获事件(通常不捕获)的布尔值。在前面的示例中,我们对每个事件调用该方法,传递我们编写来处理事件的函数。我们的每个函数只是记录了事件在 HTML 内容中的 debug div 中已接收的事实。

还有更多...

XMLHttpResult对象定义了一个属性onreadystatechange,你可以向其分配一个函数,该对象在请求运行期间会定期调用此函数。下一个食谱,《解析返回的 JSON》描述了如何使用此功能来监控请求的状态。

这些事件的行为在不同的浏览器之间以及浏览器版本之间都有所不同。例如,微软 Internet Explorer 的早期版本(版本 9 之前)根本不支持这些事件。如果你的网页应用程序要在多个浏览器上运行,特别是如果它们是不同版本的情况下,你应该采取最低公倍数的方法来处理这些事件。

另见

由于对这些事件的支持因浏览器和浏览器版本而异,因此在使用这些事件方面使用像 jQuery 或 AngularJS 这样的 JavaScript 框架确实很有帮助。这些框架抽象了特定的浏览器差异。第四章讨论了使用这些框架进行 AJAX 的方法。

请参阅第四章中的使用 jQuery 和 AngularJS 获取异步请求的进度和使用 AngularJS 获取异步请求的进度,了解响应这些事件的浏览器无关方法。

解析返回的 JSON 数据

一旦服务器返回结果,你需要一种方法从 XMLHttpRequest 对象中获取该结果,并将结果从字符串转换为 JavaScript 对象。

如何做到...

XMLHttpRequest 对象定义了 onreadystatechange 属性,你将一个函数分配给它,这个函数在整个请求的生命周期中定期被调用。以下是完整的 doAjax 函数,包括分配给这个属性的一个函数,用于监视请求的完成情况:

function doAjax() {
  var xmlhttp;
  xmlhttp = new XMLHttpRequest();

  var request = { 
    call: "kf6gpe-7"
  };

  xmlhttp.addEventListener("loadstart", loadstart, false);
  xmlhttp.addEventListener("progress", progress, false);  
  xmlhttp.addEventListener("load", load, false);
  xmlhttp.addEventListener("abort", abort, false);
  xmlhttp.addEventListener("error", error, false);

 xmlhttp.onreadystatechange = function() {
 if (xmlhttp.readyState == 4 &&xmlhttp.status == 200)
 {
 var result = JSON.parse(xmlhttp.responseText);
 document.getElementById("json").innerHTML = 
 xmlhttp.responseText;
 document.getElementById("result").innerHTML = result.call + ":" 
+ result.lat + ", " + result.lng;
 }
 };

xmlhttp.open("POST","/", true);
xmlhttp.setRequestHeader("Content-type","application/json");
xmlhttp.send(JSON.stringify(request));
}

它是如何工作的…

在添加了各种事件监听器之后,我们将一个函数分配给 onreadystatechange 属性。每当请求对象的状态发生变化时,这个函数就会被调用;每次调用时,我们测试请求对象的 readyState 字段及其状态。readyState 字段表示请求的状态;我们关注的状态是 4,它表示请求已完成。一旦请求完成,我们可以在请求的 status 字段中找到请求的 HTTP 状态;HTTP 状态码 200 表示从服务器读取内容的成功状态。

一旦我们得到 readyState 4 和 HTTP 状态 200,我们定义了一个新变量 result 作为由服务器返回的 JSON 解析后的对象,该对象可从请求的 responseText 字段中获得。你可以随意处理结果对象;我们将 JSON 复制到 jsondiv,这样你就可以看到 JSON 并在创建 resultdiv 的内容时读取 JavaScript 对象的几个字段。

还有更多...

XMLHttpRequest 类定义了以下就绪状态:

  • 0 表示请求尚未初始化

  • 1 表示请求已设置

  • 2 表示请求已发送

  • 3 表示请求正在进行中

  • 4 表示请求已完成

在实际应用中,你通常应该只使用最后一个值,并用事件进行其他进度报告。

HTTP 结果代码在 HTTP 请求的注释中定义,互联网 RFC 2616;您对此感兴趣的部分位于www.w3.org/Protocols/rfc2616/rfc2616-sec10.html。200 系列的结果表示事务成功;您如何处理其他通知将取决于您 Web 应用程序的业务逻辑。

最终的 Node.js 服务器看起来像这样:

var http = require('http');
var fs = require('fs');
var url = require('url');
var request = require("request");

console.log("Starting");

http.createServer(function(req, res) {
  if (req.method == 'POST') {
    console.log('POST');
    var body = '';
    req.on('data', function(data) {
      body += data;
    });
    req.on('end', function() {         
      var json = JSON.parse(body);
      var apiKey = "<<key>>";
      var serviceUrl = "http://api.aprs.fi/api/get?name=" + 
      json.call + "&what=loc&apikey=" + apiKey + "&format=json"; 
      request(serviceUrl, function(error, response, body) {
        var bodyObject = JSON.parse(body);
        if (bodyObject.entries.length>0)
        {
          json.call = bodyObject.entries[0].name;
          json.lat = bodyObject.entries[0].lat;
          json.lng = bodyObject.entries[0].lng;
          json.result = "OK";
        }
        else
        {
          json.result = "ERROR";
        }
        res.writeHead(200, {'Content-Type': 'application/json'});
        res.end(JSON.stringify(json));
      });
    });
  } 
  elseif (req.method == 'GET') 
  {
    console.log('GET');
    var urlParts = url.parse(req.url);
    if (urlParts.pathname == "/favicon.ico")
    {
      res.end("");
      return;
    }
    res.writeHead(200, {'Content-Type': 'text/plain'});
    var html = fs.readFileSync('./public' + urlParts.pathname);
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(html); 
    return;    
  }
}).listen(1337, 'localhost');
console.log('Server running at http://localhost:1337');

使用 Node.js 发出 Web 服务请求

到目前为止,我们的服务器对POST请求的响应不做太多事情;它所做的就是返回“OK”并将客户端的 JSON 返回给客户端。通常,您的服务器需要对您提供的 JSON 做些事情,例如,进行 Web 或数据库查询,或者执行计算。我们的示例查询位于www.aprs.fi/的 Web 服务 JSON 端点,让您了解如何使用 Node.js 进行服务器到服务器的 Web 服务请求。

准备

如果您想亲自运行示例,首先需要去www.aprs.fi注册一个账户,并获得一个 API 密钥。按照页面上的链接进行操作,并将随后的示例中的文本"—key-"替换为您的 API 密钥。

如何做到...

我们的 Node.js 代码将构建一个包含我们感兴趣的车站标识符和我们的 API 密钥的 URL,并代表客户端发出额外的 HTTP 请求。它看起来像这样:

var request = require('server');

///...

if (req.method == 'POST') {
  console.log('POST');
  var body = '';
  req.on('data', function(data) {
    body += data;
  });
  req.on('end', function() {     
     var json = JSON.parse(body);
 var apiKey = "—key-";
 var serviceUrl = "http://api.aprs.fi/api/get?name=" + 
 json.call + 
 "&what=loc&apikey=" + apiKey + 
 "&format=json";

 request(serviceUrl, function(error, response, body) {
 var bodyObject = JSON.parse(body);
 if (bodyObject.entries.length>0)
 {
 json.call = bodyObject.entries[0].name;
 json.lat = bodyObject.entries[0].lat;
 json.lng = bodyObject.entries[0].lng;
 json.result = "OK";
 }
 else
 {
 json.result = "ERROR";
        }
        res.writeHead(200, 
          {'Content-Type': 'application/json'});

        res.end(JSON.stringify(json));
      });
    });
  } 
  elseif (req.method == 'GET')
  {
    // …Original GET handling code here…
  }
}).listen(1337, 'localhost');
console.log('Server running at http://127.0.0.1:1337');

它是如何工作的…

在将客户端 JSON 转换为 JavaScript 对象后,代码创建了一个包含请求车站标识符、API 密钥以及我们想要为结果获取 JSON 的 Web 请求 URL。然后我们使用request方法向该 URL 发出简单的GET请求,并传递一个 Node.js 将在请求成功时调用的函数。

Node.js 用错误指示器、包含 HTTP 响应详情的响应对象以及请求返回的正文调用我们的回调函数。在此示例中,我们假设成功以节省篇幅,并使用JSON.parse将结果正文从 JSON 转换为 JavaScript 对象。结果对象是一个类似于您在介绍部分看到的第一章,在客户端读写 JSON中的 JavaScript 对象。它有一个 entries 数组,有零个或多个记录,指示记录中的lat和lng字段中每个车站的位置。我们提取返回的第一个结果并将相关数据复制到我们将返回给原始客户端的 JavaScript 对象中。

还有更多...

大多数服务器端框架提供了各种修改 Web 服务请求语义的方法,包括指定头部和发出请求时使用的 HTTP 方法。Node.js 的请求模块也不例外。

首先,请求方法可以接受一个 JavaScript 对象,而不是一个 URL,其中有多个字段允许您自定义请求。如果您传递一个对象,您应该将请求应发送到的 URL 放在 URI 或 URL 属性中。您还可以指定以下内容:

  • 要使用的 HTTP 方法,通过 method 参数传入

  • 要发送的 HTTP 头,作为具有每个头属性-值对的 JavaScript 对象,在 attribute headers 中传入每个头

  • 对于PATCH、POST和PUT方法请求,要传递给客户端的正文,在 body 属性中传入

  • 超时时间,在超时属性中以毫秒为单位表示等待多长时间

  • 是否对响应进行 gzip 压缩,通过设置 gzip 属性为true来指示

还有其他选项可供选择。详情请参阅 Node.js 文档,网址为nodejs.org/api/index.html。

另见

Node.js 请求模块的文档在 GitHub 上,网址为github.com/request/request

第四章.使用 jQuery 和 AngularJS 在 AJAX 应用程序中使用 JSON

在本章中,我们将探讨 JSON 在提供比旧网页更好的响应性的异步 JavaScript 和 XML(AJAX)应用程序中所起的作用。在本章中,您将找到以下食谱:

  • 在您的网页中添加 jQuery 依赖关系

  • 使用 jQuery 请求 JSON 内容

  • 使用 jQuery 将 JSON 发送到您的网络服务器

  • 使用 jQuery 获取请求的进度

  • 使用 jQuery 解析返回的 JSON

  • 在您的网页中添加 AngularJS 依赖关系

  • 使用 AngularJS 请求 JSON 内容

  • 使用 AngularJS 将 JSON 发送到您的网络服务器

  • 使用 AngularJS 获取请求的进度

  • 使用 AngularJS 解析返回的 JSON

简介

在上一章中,您看到了展示如何使用XMLHttpRequest来制作交换 JSON 的 AJAX 请求的食谱。在实际中,处理不同浏览器中的所有特殊情况使得这项工作变得繁琐且容易出错。幸运的是,大多数客户端 JavaScript 框架为您包装了这个对象,为您提供了一种与浏览器无关的方法来做同样的事情。通常,这个界面也更容易使用——正如您即将看到的,在 AngularJS 的情况下,您不需要做任何特别的事情就可以使用 JSON 在对象之间移动;该框架甚至为您处理 JSON 的序列化和反序列化!

both AngularJS 和 jQuery 都是使开发网络应用程序更简单的客户端 JavaScript 框架。jQuery 是第一个也是最受欢迎的框架之一;AngularJS 是较新的,并且具有提供使用模型-视图-控制器(MVC)范式的额外优势,使您的代码结构更加清晰。

提示

MVC 是一种设计模式,可以追溯到几十年以前,最初是在 20 世纪 70 年代的 Smalltalk 中引入的。这种模式将您的代码分为三个不同的部分:模型,包含用户想要操作的数据;视图,显示模型的内容;控制器,接受事件并在接受的事件发生时更改模型。

在本章中,我们将使用我们在上一章的食谱中基于的 Node.js 服务器,并扩展支持提供客户端 JavaScript 以及 HTML。以下是本节的代码,逐步分解如下:

var http = require('http');
var fs = require('fs');
var url = require('url');
var request = require("request");

这四行包括了我们的服务器需要的接口——处理 HTTP 服务器模块、文件系统模块、URL 解析模块以及一个简单的模块来发送 HTTP 请求。

接下来,我们记录服务器启动的情况,并创建一个 HTTP 服务器,它用一个函数回调接受所有请求:

console.log("Starting");
http.createServer(function(req, res) {

我们的服务器处理两种类型的请求:POST请求和GET请求。POST请求处理程序需要读取被发送到服务器的传入数据,我们通过将其与一个最初为空的body缓冲区连接起来来实现:

  if (req.method == 'POST') {
    console.log('POST');
    var body = '';
    req.on('data', function(data) {
      body += data;
    });

我们注册了一个函数,当 Node.js 完成 HTTP POST 请求时会调用它,该函数解析 JSON 并对远程服务器发起GET请求以获取我们的数据,模拟中间件服务器可能会执行的操作:

    req.on('end', function() {         
      var json = JSON.parse(body);

      var apiKey = " --- api key here --- ";
      var serviceUrl = "http://api.aprs.fi/api/get?name=" + 
        json.call + "&what=loc&apikey=" + apiKey + "&format=json";

这个请求本身有一个回调,它解析来自远程服务器的传入 JSON,在结果条目属性中查找数组的第一个元素,并构造一个 JSON 对象以返回给 Web 客户端。如果我们没有得到有效的响应,我们设置一个错误值,以便客户端可以对错误做些什么。我们通过将 JavaScript 对象转换为 JSON 并将其写入客户端来返回这个:

      request(serviceUrl, function(error, response, body) {
        var bodyObject = JSON.parse(body);
        if (bodyObject.entries.length>0)
        {
          json.call = bodyObject.entries[0].name;
          json.lat = bodyObject.entries[0].lat;
          json.lng = bodyObject.entries[0].lng;
          json.result = "OK";
        }
        else
        {
          json.result = "ERROR";
        }
        res.writeHead(200, {'Content-Type': 'application/json'});
        res.end(JSON.stringify(json));
      });
    });
  } 

如果我们处理的不是POST请求,那它可能是一个GET请求。以下是上一章的新代码。我们需要确定传入的 URL 是否表示要获取的内容是 HTML 文件(其扩展名为.html或.htm)还是 JavaScript 文件(其扩展名为.js)。首先,我们检查是否正在请求一个 favicon;Chrome 总是这样做,我们只是返回一个空的对象体。假设请求的不是 favicon,我们检查传入的 URL 如何结束,以便我们可以写出适当的内容类型头(text/html 或 application/json)。如果不是这些,我们假设是纯文本,并发送一个 text/plain 内容类型头:

  else if (req.method == 'GET') 
  {
    console.log('GET');
    var urlParts = url.parse(req.url);
    if (urlParts.pathname == "/favicon.ico")
    {
      res.end("");
      return;
    }

    if (urlParts.pathname.lastIndexOf(".html") == 
          urlParts.pathname.length - 5 ||
        urlParts.pathname.lastIndexOf(".htm") == 
          urlParts.pathname.length - 4)
    {
      res.writeHead(200, {'Content-Type': 'text/html'});
    }
    else if (urlParts.pathname.lastIndexOf(".js") == 
      urlParts.pathname.length - 3)
    {
      res.writeHead(200, {'Content-Type': 'application/json'});
    }
    else
    {
      res.writeHead(200, {'Content-Type': 'text/plain'});            
    }

接下来,我们从 Node.js 服务器源下面的公共目录中读取内容并返回给客户端:

    var c = fs.readFileSync('./public' + urlParts.pathname);
    res.end(c); 
    return;    
  }

最后,这个大函数作为监听 HTTP 服务器注册在本地主机的端1337上,我们记录服务器已启动:

}).listen(1337, 'localhost');
console.log('Server running at http://localhost:1337');

提示

一个真正的服务器可能不应该通过查看传入的 URL 来猜测返回数据的 MIME 类型,而应该实际上嗅探出去的数据并做出关于 MIME 类型的决定。有一个 Node.js 模块 magic 可以做到这一点;如果您稍微不那么偏执,可以使用磁盘上的文件名后缀,并希望内容提供商正确地命名文件。

这就是服务器的内容,您可以在随书附带的样本 ZIP 文件中找到它。

向您的网页添加 jQuery 依赖

jQuery 是一个流行的客户端框架,用于 AJAX 应用程序,它为您提供了浏览器无关的支持,用于搜索和操作文档对象模型(DOM)和层叠样式表(CSS),执行 AJAX 查询,以及包括几个可以使用 CSS 样式的 HTML 控件。您需要在您的页面中包含 jQuery 的源代码,要么通过指向 jQuery 内容分发网络(CDN)上的发布版本,要么通过访问www.jquery.com并下载框架的副本,以便与您自己的应用程序一起使用。

如何做到这一点...

您需要通过开始一个新的 json-example.html 文件来包含 jQuery 库,像这样:

<!doctype HTML>
<html>
<head>
  <script type="text/javascript"
    src="img/jquery-1.11.2.min.js"></script>
</head>

它是如何工作的…

这两行包含了两个包含从 jquery.com CDN 获取的 jQuery 客户端库压缩版本的脚本。这可能正是你在生产应用程序中想要做的事情;压缩的 jQuery 实现比完整的库要小,所以客户端下载更快,使用 CDN 上的版本提供的性能可能比你自己能提供的性能还要快,除非你在像 Amazon Web Services 或 Microsoft Azure 这样的主要云服务提供商上托管多个服务器。

还有更多…

如果你不想包含压缩版本——这通常在你深入开发周期并希望调试代码时发生——你可以从你的服务器上提供标准版本。只需从www.jquery.com/下载必要的文件,并从你的服务器上提供它们。

jQuery 有两个版本:1.x 版本,支持较老的浏览器,包括 Microsoft Internet Explorer 6 及以上版本,而 2.x 版本至少需要 Microsoft Internet Explorer 9。我们的示例将使用 jQuery 1.x,但不用担心;我们讨论的 API 在 jQuery 2.x 中也是一样的。

参见

前往www.jquery.com下载 jQuery 或了解更多关于它的信息。如果你正在寻找一个 JavaScript 框架,也许值得查看 jQuery 学习中心在learn.jquery.com/的内容,或者也许可以看看 Packt Publishing 的书籍,《学习 jQuery – 第四版》,作者是 Jonathan Chaffer 和 Karl Swedberg。

使用 jQuery 请求 JSON 内容

jQuery 定义了变量$,暴露了你想要与界面做的所有方法的接口。(有一种方法可以重命名该变量,比如说如果你正在与其他使用相同变量的 JavaScript 环境一起工作,但我建议不要这样做)。$暴露的方法之一是ajax方法,你可以用它来发起 AJAX 查询。让我们来看看它是如何做到的。

如何做到…

这是一个整页的 AJAX 请求。AJAX 代码是粗体的:

<!doctype HTML>
<html>
<head>
<script  type="text/javascript"
  src="img/"></script>
</head>
<body>

<p>Hello world</p>
<p>
  <div id="debug"></div>
</p>
<p>
  <div id="json"></div>
</p>
<p>
  <div id="result"></div>
</p>

<p>Powered by <a href="http://www.aprs.fi">aprs.fi</a></p>

<script>
$(function () {
 $('#debug').html("loaded... executing.");

 var request = { 
 call: "kf6gpe-7"
 };

 $.ajax({
 type: "POST",
 url: "/",
 dataType:"json"  });
});

</script>
</body>
</html>

这个例子中的 HTML 很简单。它包含了 jQuery 模块,然后为 AJAX 请求定义了三个div区域,在请求完成后更新。让我们更详细地看看 JavaScript 函数doAjax。

它是如何工作的…

doAjax函数,在页面加载完成后调用,首先将名为debug的div的 HTML 内容设置为文本"loaded… executing."。$()语法是 jQuery 用来在 DOM 中查找项目的语法;你可以通过在名称前加上#(哈希)符号来找到项目,就像 CSS 选择器一样。返回的值不是实际的 DOM 元素,而是一个包含简单方法如html以获取或设置项目 HTML 内容的 jQuery 类,该类包装了 DOM 元素。

接下来,我们定义一个 JSON 对象,其中包含我们请求的详细信息,就像前章的食谱中所做的那样。它有一个属性,call,包含我们感兴趣的站的呼号。

接下来,我们调用$的ajax方法,传递一个具有我们请求语义的 JavaScript 对象。它应该包含以下字段:

  • type字段,表示请求的 HTTP 方法(如POST或GET)。

  • url字段,表示请求应提交的 URL。

  • data字段,包含要发送到服务器的请求(如果有)的字符串数据。我们将在下一个食谱中看到它的使用。

  • dataType字段,表示你期望从服务器获得的数据类型;一个可选字段,可以是xml、json、script或html。

参见 also

好奇的读者应该查阅 jQuery ajax方法文档,该文档可在api.jquery.com/jQuery.ajax/找到。

使用 jQuery 将 JSON 发送到你的网络服务器

使用 jQuery 将 JSON 发送到你的服务器是很容易的。只需获取 JSON 格式的数据,并使用ajax方法参数的data字段指定它。

如何做到…

让我们再次看看doAjax,这次修改以发送我们的 JSON 请求:

function doAjax() {
  $('#debug').html("loaded... executing.");

  var request = { 
    call: "kf6gpe-7"
  };

  $.ajax({
    type: "POST",
    url: "/",
    data: JSON.stringify(request),
    dataType:"json"
  });
}

</script>
</body>
</html>

它是如何工作的…

上一列表中的魔法行被突出显示;它是传递给ajax方法的参数中的以下行:

    data: JSON.stringify(request),

当然,我们使用JSON.stringify将 JavaScript 对象编码为 JSON,然后将其分配给 data 字段。

使用 jQuery 获取请求进度的方法

jQuery 以一种与平台无关的方式抽象化了底层XMLHttpRequest对象的各个进度报告机制,赋予您确定您的请求是否成功或失败的能力。您通过注册函数来实现,这些函数将在发生错误或结果成功加载时由 jQuery AJAX 处理程序调用。

如何做到…

下面是doAjax重写以支持在失败时获取通知的代码,无论事件成功还是失败:

function doAjax() {
  $('#debug').html("loaded... executing.");

  var request = { 
    call: "kf6gpe-7"
  };

  $.ajax({
    type: "POST",
    url: "/",
    data: JSON.stringify(request),
    dataType:"json",
  })
 .fail(function() {
 $('#debug').append("<br/>failed");
 })
 .always(function() {
 $('#debug').append("<br/>complete");
 });
}

这里的新方法是fail和always方法。

它是如何工作的…

jQuery 使用一种称为链式调用的模式,其中大多数方法返回一个实例,您可以对该实例应用其他方法。因此,像fail和always这样的方法在同一个对象上操作,并返回相同的对象,该对象使用链式调用封装了$.ajax方法调用的返回值,使得代码更易读、更易写。在$.ajax的情况下,返回的是一个 jQuery XMLHttpRequest对象的实例,其字段是浏览器返回的XMLHttpRequest对象的超集。

在这里,我在$.ajax的返回值上设置了两个事件处理程序:一个是用于失败情况的,即请求因某些原因失败;另一个是用于始终情况的。请注意,由于链式调用的存在,我可以将这些处理程序颠倒过来,将始终情况的处理程序放在前面,将失败情况的处理程序放在后面。究竟哪个在前完全取决于你的个人喜好。

always和failure方法都接受一个函数,该函数可以接受多达三个参数。在这种情况下,我没有使用任何可用的参数,只是将一些文本添加到具有id为 debug 的div区域的 HTML 中。当请求成功完成时,jQuery 将failure事件处理程序传递给 jQuery XMLHttpRequest对象,以及与失败相关的文本状态消息和错误代码,而将always方法传递给错误情况下的这些参数,或者传递给数据、文本状态消息和 jQuery XMLHttpRequest对象。

还有更多…

如果你愿意,你可以在$.ajax的初始 JavaScript 对象参数的名为 error 的属性中指定失败事件处理程序作为一个函数。同样,你也可以在初始 JavaScript 对象的名为complete的属性中指定始终事件处理程序作为一个函数。虽然这样可以将在一个地方放置所有代码,但我个人认为这样更难读,因为缩进可能会很快变得难以控制。

使用 jQuery 解析返回的 JSON

最后,是时候看看如何从服务器获取返回的 JSON 并使用它了。你会通过在$.ajax上注册一个事件处理程序来接收结果的 JavaScript 对象,jQuery 会为你从 JSON 中很乐意地反序列化这个对象。

如何做到…

为了从 AJAX 请求中获取结果,我们需要在 jQuery XMLHttpRequest对象的done事件上添加一个事件处理程序,如下所示:

function doAjax() {
  $('#debug').html("loaded... executing.");

  var request = { 
    call: "kf6gpe-7"
  };

  $.ajax({
    type: "POST",
    url: "/",
    data: JSON.stringify(request),
    dataType:"json",
  })
  .fail(function() {
    $('#debug').html( $('#debug').html() + "<br/>failed");
  })
  .always(function() {
    $('#debug').html( $('#debug').html() + "<br/>complete");
  })
 .done(function(result) {
 $('#json').html(JSON.stringify(result));
 $('#result').html(result.call + ":" + 
 result.lat + ", " + result.lng);
 });
}

它是如何工作的…

jQuery 在请求成功完成时调用done事件处理程序,并将结果数据作为参数传递。因为我们已经在对$.ajax的初始调用中指定了数据类型为json,jQuery 很乐意使用JSON.parse来解析返回值,并传递我们感兴趣的 JavaScript 对象,从而省去了我们自己的parse调用。

我们的done事件处理程序做两件事:它将对象的字符串化 JSON(由浏览器串行化,而不是服务器返回)放入 ID 为json的div字段中,并将结果div更新为从结果数据中获取的电台呼号、纬度和经度。这样我们就得到了一个看起来像这样的网页:

它是如何工作的…

还有更多…

如果你愿意,可以通过将事件处理程序作为初始请求的success字段传递给$.ajax来注册事件处理程序。像fail和always一样,我更喜欢使用链式调用来显式设置它,因为我认为这样更易读。

向你的网页添加 AngularJS 依赖项

就像其他的 JavaScript 框架一样,您需要在您的 HTML 中包含 AngularJS。正如您在本节中将要看到的,为了设置还需要做一些其他不同的事情。首先,确保您创建了一个新的 HTML 文件,比如json-example-angular.html。

如何做到…

以下是我们的应用程序的完整 HTML:

<!doctype HTML>
<html>
  <head>
  </head>

<body ng-app="aprsapp">
  <div ng-controller="AprsController">
    <button ng-click="doAjax()">Send AJAX Request</button>
    <div>{{debug}}</div>
    <div>{{json}}</div>
	 <br/>
        <div>{{message}}<div>
  </div>

  <p>Powered by <a href="http://www.aprs.fi">aprs.fi</a></p>
<script type="text/javascript"
src="img/angular.min.js"></script>
<script src="img/json-example-angularjs.js"></script>
</body>
</html>

让我们更仔细地看看这个 HTML,看看有什么不同。

它是如何工作的…

首先,请注意body标签具有ng-app属性,其设置为aprsapp。AngularJS 应用程序被赋予了定义好的名称,你在实现应用程序逻辑的 JavaScript 中引用这些名称。

接下来,请注意包含我们 UI 的div区域具有ng-controller属性,它标识了负责处理该 UI 部分事件的具体控制器模块。我们马上就会看到它是如何与 JavaScript 相链接的。在那个div中有其他div区域,其内容包含在双括号中,定义了一个文档模板,Angular.js 为您填充。这是 AngularJS 中的一个变量;在控制器加载时,HTML 中的这些变量将被控制器设置的内容所替换。每个都是一个模型,包含要显示的数据。

最后,我们需要包含 AngularJS 模块本身以及我们的 JavaScript。在使用 AngularJS 时,习惯上将您的应用程序的 JavaScript 保存在单独的文件中,因为这有助于您强制执行良好的应用程序外观(包含在您的 HTML 和 CSS 中)和实现(包含在您的 JavaScript 中)之间的分离。

现在,让我们看看我们页面的 JavaScript 骨架,我们将其放在json-examnple-angular.js文件中:

var app = angular.module("aprsapp", []);

app.controller("AprsController", , ["$scope",
  function($scope) {
  $scope.json = "";
  $scope.message = "Loaded..."; 
}]);

这段代码定义了一个单独的 AngularJS 应用程序,名为aprsapp。请注意,这个名字必须与您 body 标签中ng-app属性的名称相匹配。代码然后为应用程序注册了一个控制器,名为AprsController。控制器是一个函数,至少有一个参数,即控制器的范围,您在那里定义您的数据模型和其他变量。在我们的控制器范围内,我们设置了两个模型的初始值:json和message。

参见 also

要开始使用 AngularJS,请查看其网站angularjs.org,或者由Rodrigo Branas编写、Packt Publishing出版的AngularJS Essentials一书。

使用 AngularJS 请求 JSON 内容

Angular 定义了一个核心对象$http,您使用它对远程服务器进行 HTTP 请求。当你初始化它的时候,它会传递给你的控制器。

如何做到…

让我们扩展我们的控制器,以添加对$http对象的引用并使用它来发送请求:

var app = angular.module("aprsapp", []);

app.controller("AprsController", ["$scope", "$http",
function($scope, $http) {
  $scope.json = "";
  $scope.message = "Loaded..."; 
  $scope.doAjax = function()
  {
    $scope.debug = "Fetching...";    
    $scope.json= "";
    $scope.message = "";

    var promise = $http({
      url: "/", 
      method: "POST",
    });
  };
}]);

在这里,我们在我们的范围内定义了一个函数doAjax,它将执行异步 HTTP 请求。它更新了我们的模型,使debug模型包含一个状态消息,而json和message模型为空字符串。让我们更详细地看看$http对象。

它是如何工作的…

查看控制器定义函数,你可以看到我们不仅传递了控制器的范围,还传递了$http对象。它定义了一个函数,接受一个参数,一个定义 HTTP 请求参数的 JavaScript 对象。在我们的示例中,我们通过将method字段设置为POST并将url字段设置为/,请求向服务器的根发送一个POST请求。

$http方法的参数可以包括这些属性:

  • method属性,指示要使用的 HTTP 方法。

  • url属性,指示方法应该发送到的 URL。

  • params属性是一个字符串或对象的映射,用于发送到服务器;如果值不是字符串,它将被编码为 JSON(关于这一点将在下一个食谱中详细介绍);params属性被附加到 URL 上。

  • data属性,是要发送到远程服务器的数据。

  • headers属性,是一个要发送到远程服务器的标题和标题值的映射。

  • timeout属性,指示等待响应的时间长度。

$http()方法返回一个承诺,当你成功发送数据时,你会在这个对象上调用其他方法来注册事件处理程序来检测错误和处理数据。(我们将在食谱《使用 AngularJS 获取请求进度》和《使用 AngularJS 解析返回的 JSON》中进一步讨论承诺。)

还有更多...

$http对象还定义了单独的方法get、post、put、delete和patch,用于发出适当的 HTTP 请求。如果你愿意,你可以使用它们代替$http()方法,省略method属性。像$http()一样,它们都返回一个承诺。

参见

有关$http()方法和 AngularJS 对 AJAX 的支持的文档,请参阅docs.angularjs.org/api/ng/service/$http。

使用 AngularJS 向你的 Web 服务器发送 JSON

使用 AngularJS 发送 JSON 就像在$http()方法调用中提供data属性一样简单。AngularJS 甚至会为你编码对象为 JSON。

如何做到这一点...

像以前一样,我们将发起一个 AJAX 请求。这次,我们包含了一个data属性:

var app = angular.module("aprsapp", []);

app.controller("AprsController", ["$scope", "$http",
function($scope, $http) {
  $scope.json = "";
  $scope.message = "Loaded..."; 
  $scope.doAjax = function()
  {
    $scope.debug = "Fetching...";    
    $scope.json= "";
    $scope.message = "";
 var request = { 
 call: "kf6gpe-7"
 };
    var promise = $http({
      url: "/", 
      method: "POST",
 data: request
    });
  };
}]);

它是如何工作的…

我们像过去例子中一样定义 JavaScript 对象请求,单个调用属性包含我们感兴趣的站的呼号。通过将这个值作为数据属性传递给$http()的参数,AngularJS 将对象转换为 JSON 并发送给服务器。

还有更多...

如果你使用$http.post()这样的方法,将数据作为第二个参数传递,像这样:

$http.post("/", request);

你还可以通过第三个参数传递一个可选的配置参数。这样的配置对象将包含我在前一个食谱中描述的请求对象的属性。

使用 AngularJS 获取请求进度

$http()方法返回一个承诺,这是您确定请求状态的方式。它定义了方法,您可以将 JavaScript 函数传递给这些方法,当底层网络事务状态改变时,这些函数作为事件处理程序运行。

如何做到…

返回的承诺定义了success和error方法,这些方法需要事件处理程序。要使用它们,我们编写以下代码:

var app = angular.module("aprsapp", []);

app.controller("AprsController", ["$scope", "$http",
function($scope, $http) {
  $scope.json = "";
  $scope.message = "Loaded..."; 
  $scope.doAjax = function()
  {
    $scope.debug = "Fetching...";    
    $scope.json= "";
    $scope.message = "";
    var request = { 
      call: "kf6gpe-7"
    };
    var promise = $http({
      url:"/", 
      method: "POST",
      data: request
    });
    promise.success(function(result, status, headers, config) {
      // handle success here
    });
    promise.error(function(data, status, headers, config) {
      alert("AJAX failed!");
    });
}]);

它是如何工作的…

在成功时,AngularJS 使用success方法调用您注册的承诺函数,并传递结果数据、HTTP 状态、HTTP 头和与请求关联的配置。在这里,您将处理网络事务的结果,我们将在下一个菜谱中更详细地讨论。在任何类型的失败时,AngularJS 都会调用您用error方法注册的回调,并传递相同的数据显示。

请注意success和error方法又返回了承诺,所以如果您愿意,可以链接这些请求。

使用 AngularJS 解析返回的 JSON

使用 AngularJS 处理返回的数据很容易,因为它为您解析返回的 JSON,并将结果对象传递给您注册的事件处理程序。

如何做到…

以下是我们的 AngularJS 应用程序完整的客户端代码。success承诺的回调只是用我们从结果中获取的对象字段更新模型:

var app = angular.module("aprsapp", []);

app.controller("AprsController", function($scope, $http) {
  $scope.json = "";
  $scope.message = "Loaded..."; 
  $scope.doAjax = function()
  {
    $scope.debug = "Fetching...";    
    $scope.json= "";
    $scope.message = "";
    var request = { 
      call: "kf6gpe-7"
    };

    var promise = $http({
      url:"/", 
      method: "POST",
      data: request
    });
    promise.success(function(result, status, headers, config) {
      $scope.debug = "Loaded.";    
      $scope.json = result;
      $scope.message = result.call + ":" + result.lat + ", " + 
        result.lng;
    });
    promise.error(function(data, status, headers) {
      alert("AJAX failed!");
    });
}]);

它是如何工作的…

由于 AngularJS 处理 JSON 解析,因此在填充消息模型中的文本时,我们可以直接反引用的返回 JSON 中的值。注意,我们还可以将 JSON 模型分配给结果对象,当显示此对象时,它将显示结果对象本身的 JSON。

如果您在 Chrome 中加载 HTML 和 JavaScript 并按下调用doAjax的按钮,您应该会看到类似这样的内容:

它是如何工作的…

第五章:使用 MongoDB 的 JSON

在本章中,我们将介绍以下食谱:

  • 设置 MongoDB

  • 为 Node.js 安装 MongoDB 数据库驱动程序

  • 为 Node.js 安装 express 模块

  • 使用 Node.js 连接 MongoDB 数据库

  • 使用 Node.js 在 MongoDB 中创建文档

  • 使用 Node.js 在 MongoDB 中搜索文档

  • 使用 Node.js 在 MongoDB 中更新文档

  • 使用 Node.js 在 MongoDB 中删除文档

  • 使用 REST 搜索 MongoDB

  • 使用 REST 在 MongoDB 中创建文档

  • 使用 REST 更新 MongoDB 中的文档

  • 使用 REST 在 MongoDB 中删除文档

简介

在本章中,我们将介绍如何使用 MongoDB 作为 Web 应用程序的后端存储。虽然不是完全专注于 JSON,但正如你所见,本章的食谱将帮助你管理使用 MongoDB 在 Node.js 中直接创建、读取、更新和删除文档,然后使用为 Node.js 和 MongoDB 构建的 REST 服务器,这样你就可以从网络客户端(如 Web 应用程序)管理文档。

设置 MongoDB

安装 MongoDB 取决于平台;在 Linux 上,你可能可以使用像 apt 这样的包安装器,而在 Windows 和 Mac OS X(以及如果你有没有包含 MongoDB 包的包管理器的 Linux 发行版)上,可以使用网页下载。

如何做到…

  1. 在 Mac OS X 和 Windows 上,只需前往www.mongodb.org/并点击下载链接。在撰写本文时,MongoDB 处于 2.6.7 版本;有一个 3.0 版本的候选发布,我们在这里不再进一步讨论。

    Mongo 还提供了针对几种常见 Linux 发行版的包,包括 Debian 和 Fedora。还有一个适用于 FreeBSD 的包。

  2. 一旦你下载并安装了 Mongo,你需要为 MongoDB 提供一个存储数据库的地方。

    这取决于平台;在 Windows 上,它是c:\data\db.

  3. 一旦你这样做,你可以通过运行mongod来启动数据库服务器。你可能还想将 MongoDB 客户端和服务器二进制文件的路径添加到你的路径中,这样你就可以从命令行轻松访问它们。

  4. 当你运行 MongoDB 服务器时,你应该会看到一堆类似于这样的日志消息:

    C:\Program Files\MongoDB 2.6 Standard\bin\mongod.exe --help for help and startup options
    2015-02-15T13:10:07.909-0800 [initandlisten] MongoDB starting : pid=13436 port=27017 dbpath=\data\db\ 64-bit host=KF6GPE-SURFACE
    2015-02-15T13:10:07.911-0800 [initandlisten] targetMinOS: Windows 7/Windows Server 2008 R2
    2015-02-15T13:10:07.913-0800 [initandlisten] db version v2.6.7
    2015-02-15T13:10:07.914-0800 [initandlisten] git version: a7d57ad27c382de82e9cb93bf983a80fd9ac9899
    2015-02-15T13:10:07.915-0800 [initandlisten] build info: windows sys.getwindowsversion(major=6, minor=1, build=7601, pla
    tform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49
    2015-02-15T13:10:07.917-0800 [initandlisten] allocator: system
    2015-02-15T13:10:07.920-0800 [initandlisten] options: {}
    2015-02-15T13:10:07.930-0800 [initandlisten] journal dir=\data\db\journal
    2015-02-15T13:10:07.931-0800 [initandlisten] recover : no journal files present, no recovery needed
    2015-02-15T13:10:07.967-0800 [initandlisten] waiting for connections on port 27017
    

    你可能会注意到服务器正在运行的主机名(在这个例子中,KF6GPE-SURFACE)和端口号,默认应该是27017。

  5. 要直接连接到 MongoDB 服务器,你可以在命令行上运行mongo,像这样:

    C:\>mongo
    MongoDB shell version: 2.6.7
    connecting to: test
    >
    
  6. 要退出mongo二进制文件,请按Ctrl + C或输入exit。

它是如何工作的…

双击可执行安装程序和 Linux 包将安装 mongod 二进制文件,即数据库,以及 Mongo 命令行客户端。

安装 MongoDB 数据库驱动程序(重复)

你需要为 Node.js 安装数据库驱动程序,这样 Node.js 就可以直接与 MongoDB 服务器通信。

如何做到…

要获取数据库驱动程序,只需前往你拥有 Node.js 文件的项目的目录,并运行以下命令:

npm install mongodb

这个命令将下载数据库驱动程序并为 Node.js 安装它们。

为 Node.js 安装 express 模块

Node.js 的 express 模块使得使用 Node.js 构建表示状态转移(REST)服务器应用程序变得容易。REST 是一种在网络编程中使用的强大范式,它使用 HTTP 方法GET、POST、PUT和DELETE来管理 Web 服务的文档管理的创建、读取、更新和删除(通常缩写为 CRUD)操作。

使用 REST,URL 是表示你想要操纵什么的名词,HTTP 方法是动词,对那些名词执行动作。

在接下来的食谱中,我们将使用 Node.js 的 express 模块构建一个 RESTful 服务器,该服务器从 Mongo 返回文档,并支持基本的 CRUD 操作。在开始之前,你需要安装三个额外的模块。

如何去做…

你将使用npm,Node.js 的包管理器,来安装跨对象资源模块以支持跨域脚本,express 模块,以及 express 使用的 body-parser 模块。为此,在你的项目目录中运行以下命令:

npm install cors
npm install express
npm install body-parser

你还需要一个基本的应用程序,或者骨架,用于你的 REST 服务器,它包括 REST 服务器之间的 URL 路由、HTTP 方法以及执行必要数据库操作的函数。这个骨架包括使用 express 模块的两个 Node.js 脚本和一个 HTML 文档。

第一个 Node.js 脚本是 REST 服务器本身,位于rest-server.js中,它看起来像这样:

var express = require('express'),
  documents = require('./routes/documents'),
  cors = require('cors'),
  bodyParser = require('body-parser');

var app = express();

app.use(cors());
var jsonParser = bodyParser.json();

app.get('/documents', documents.findAll);
app.get('/documents/:id', documents.findById);
app.post('/documents', jsonParser, documents.addDocuments);
app.put('/documents/:id', jsonParser, documents.updateDocuments);
app.delete('/documents/:id', jsonParser, 
documents.deleteDocuments);

app.listen(3000);
console.log('Listening on port 3000...');

它是如何工作的…

包管理器安装每个模块,如有需要,从源代码构建它们。你需要所有三个模块:CORS 模块以支持跨域脚本请求、express 模块用于 REST 服务器框架,最后,body-parser 模块将客户端对象体从 JSON 转换为 JavaScript 对象。

骨架脚本包括 express 模块、我们的路由文件,它将定义处理每个 REST 用例的函数、CORS 模块以及 express 需要的 body-parser 模块来解释客户端发送的对象体。

一旦包含这些,它定义了一个名为app的 express 模块实例,并用 CORS 对其进行配置。这是必要的,因为默认情况下,浏览器不会对页面的内容来源不同的域名服务器发起 AJAX 请求,以防止服务器被攻陷并注入恶意 JavaScript 的跨站脚本攻击。CORS 模块为服务器设置必要的头,以便让我们可以使用上一章中的旧 Node.js 服务器在端口1337上提供内容,并让我们的内容访问在此不同端口上运行的 REST 服务器。

接下来,我们获取一个对 body-parser 的 JSON 解析器的引用,我们将用它来解析客户端为插入和更新请求发送的对象体。之后,我们用处理顶级文档 URL 的手动器配置 Express 应用服务器实例,该 URL 用于通过 REST 访问我们的 MongoDB 文档。在这个 URL 上有五种可能的操作:

  • 对 URL /documents的 HTTP GET simply returns a list of all the documents in the database

  • 对 URL /documents/<id>的 HTTP GET 返回具有给定 ID 的数据库中的文档

  • 对/documents的 HTTP POST,带有 JSON 格式的文档,将该文档保存到数据库中

  • 对/documents/<id>的 HTTP PUT,带有 JSON 格式的文档,更新具有给定 ID 的文档,使其包含客户端传递的内容

  • 对/documents/<id>的 HTTP DELETE 删除具有给定 ID 的文档

最后,脚本在端口3000上启动服务器,并记录服务器已启动的事实。

当然,我们需要在文档对象中定义函数;我们是在文件routes/documents.js中完成的,该文件最初应看起来像这样:

var mongo = require('mongodb');

var mongoServer = mongo.Server,
    database = mongo.Db,
    objectId = require('mongodb').ObjectID;

var server = new mongoServer('localhost', 27017, 
{auto_reconnect: true});
var db = new database('test', server);

db.open(function(err, db) {
  if(!err) {
    console.log("Connected to 'test' database");
    db.collection('documents', 
    {strict:true}, 
    function(err, collection) {
      if (err) {
        console.log("Inserting sample data...");
        populate();
      }
    });
  }
});

exports.findById = function(req, res) {
  res.send('');
};

exports.findAll = function(req, res) {
  res.send('');
};

exports.addDocuments = function(req, res) {
  res.send('');
};

exports.updateDocuments = function(req, res) {
  res.send('');
};

exports.deleteDocuments = function(req, res) {
  res.send('');
};

var populate = function() {
var documents = [
  {
    call: 'kf6gpe',
    lat: 37,
    lng: -122  }
];
db.collection('documents', function(err, collection) {
  collection.insert(wines, {safe:true}, 
  function(err, result) {});
  });
};

上述代码首先通过导入本地 MongoDB 驱动程序开始,设置变量以保存服务器实例、数据库实例和一个转换器接口,该接口将字符串转换为 MongoDB 对象 ID。接下来,它创建一个服务器实例,连接到我们的服务器实例(必须运行才能成功),并获得对我们数据库的引用。最后,它打开到数据库的连接,如果数据库为空,则在数据库中插入一些示例数据。(这个代码在阅读本章的前两个食谱后会更清晰,所以如果现在有些困惑,只需继续阅读,您会做得很好的!)

routes/documents.js文件的其余部分定义了处理我们在这rest-server.js脚本中连接的每个 REST 用例的函数。我们将在食谱中逐步完善每个函数。

最后,我们需要一个 HTML 文档来访问 REST 服务器。我们的文档看起来像这样:

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript"
  src="img/jquery-1.11.2.min.js"></script>
</head>
<body>

<p>Hello world</p>
<p>
<div id="debug"></div>
</p>
<p>
<div id="json"></div>
</p>
<p>
<div id="result"></div>
</p>

<button type="button" id="get" onclick="doGet()">Get</button><br/>
<form>
  Id: <input type="text" id="id"/>
  Call: <input type="text" id="call"/>
  Lat: <input type="text" id="lat"/>
  Lng: <input type="text" id="lng"/>
<button type="button" id="insert" 
    onClick="doUpsert('insert')">Insert</button>
<button type="button" id="update" 
onClick="doUpsert('update')">Update</button>
<button type="button" id="remove" 
onClick="doRemove()">Remove</button>
</form>
</body>
</html>

我们在脚本中使用一些 jQuery 来使字段访问更加容易(您将在即将到来的 REST 插入、更新、删除和查询食谱中看到脚本)。HTML 本身由三个div标签组成,分别用于调试、显示原始 JSON 和每个 REST 操作的结果,以及一个表单,让您输入创建、更新或删除记录所需的字段。

也见

关于卓越的 Node.js express 模块的更多信息,请参见expressjs.com/。

MongoDB 是一个强大的文档数据库,这里涵盖的内容远远不够。更多信息,请上网搜索,或查看 PacktPub 网站上的以下资源:

  • Instant MongoDB by Amol Nayak。

  • MongoDB Cookbook by Amol Nayak。

使用 Node.js 连接到 MongoDB 数据库

在你 Node.js 应用程序能够与 MongoDB 实例做任何事情之前,它必须通过网络连接到它。

如何做到这一点...

MongoDB 的 Node.js 驱动包含了所有必要的网络代码,用于与本地或远程机器上运行的 MongoDB 建立和断开连接。

你需要在代码中包含对原生驱动的引用,并指定要连接的数据库的 URL。

下面是一个简单的例子,它连接到数据库然后立即断开连接:

var mongo = require('mongodb').MongoClient;

var url = 'mongodb://localhost:27017/test';

mongo.connect(url, function(error, db) {
  console.log("mongo.connect returned " + error);
  db.close();
});

让我们逐行分解这个问题。

它是如何工作的…

第一行包括了 Node.js 应用程序中 Mongo 的本地驱动实现,并提取了它定义的MongoClient对象的引用。这个对象包含了与数据库通过网络交互所需的基本接口,定义了connect和close方法。

下一行定义了一个字符串url,它包含了要连接的数据库的 URL。这个 URL 的格式很简单:它以mongodb方案开始,以表示它是 MongoDB 服务器的 URL。接下来是主机名和端口(在这个例子中,我们连接到本地主机的默认端口,即27017)。最后,我们来到你想要连接的数据库的名称:在我们的例子中,是test。

如果你使用 MongoDB 的用户访问控制来控制对数据库的访问,你还需要指定一个用户名和密码。你这样做的方式和你对任何其他 URL 的做法一样,像这样:

mongodb://user:password@host:port/database

当然,是否保护你的数据库取决于你的网络结构和部署;通常来说,这样做是个好主意。

我们将这个 URL 传递给 mongo 对象的connect方法,同时提供一个函数,当连接成功建立,或者连接失败时,MongoDB 原生驱动会回调这个函数。驱动会以两个参数调用回调函数:第一个是出现错误时的错误代码(成功时为null),第二个是一个包含对你指定的数据库连接的数据库对象引用(如果建立连接时出现错误,则可能为null)。

我们的回调函数非常直接;它打印一个包含传递给它的错误代码值的消息,然后我们使用close断开与数据库的连接。

提示

当你使用完数据库对象时,总是调用其close方法,以确保原生驱动能够成功清理自身并从数据库断开连接。如果你不这么做,你可能会导致数据库连接泄露。

参见 also

关于为 Node.js 设计的 MongoDB 原生驱动的更多信息,请参阅docs.mongodb.org/ecosystem/drivers/node-js/。

使用 Node.js 在 MongoDB 中创建文档

MongoDB 数据库通过集合来组织其文档,这些集合通常是相关联的一组文档(例如表示相同种类信息的文档)。由于这个原因,您与文档交互的主要界面是通过一个集合。让我们看看如何获取一个集合并向其中添加一个文档。

提示

集合在关系型数据库中类似于一个表,但并没有规定集合中的所有文档必须具有相同的字段或每个字段相同的类型。可以将其视为一个用于分组类似文档的抽象概念。

怎么做...

以下是一个函数,它使用 Node.js 在我们的测试数据库中名为documents的集合中插入两个静态条目:

var mongo = require('mongodb').MongoClient;

var url = 'mongodb://localhost:27017/test';

var insert = function(collection, callback) {
  var documents = 
    [{ 
        call: 'kf6gpe-7', lat: 37.0, lng: -122.0 
      },
      {
        call: 'kf6gpe-9', lat: 38.0, lng: -123.0
      }];
  // Insert some documents
  collection.insert(documents, 
    function(error, result) {
      console.log('Inserted ' +result.length + ' documents ' + 
        'with result: ');
      console.log(result);
      callback(result);
  });
};

mongo.connect(url, function(error, db) {
  console.log('mongo.connect returned ' + error);

  // Get the documents collection
  var collection = db.collection('documents');
  insert(collection, function(result) {
    db.close();
  });
});

我把代码分成两部分,以便使回调结构更清晰:实际执行插入的insert函数和连接回调,该回调调用插入函数。

让我们仔细看看。

它是如何工作的...

代码的开始方式是一样的,通过获取一个对MongoClient对象的引用,它用这个对象与数据库通信。连接代码基本上也是一样的;URL 是一样的,唯一的改变是对数据库的collection方法的调用,传递我们感兴趣的集合的名称。collection方法返回一个collection对象,该对象提供了我们对文档集合执行 CRUD 操作的方法。

insert函数做几件事情。它接收一个您想要操作的集合和一个回调函数,当插入操作完成或失败时,它将调用这个回调函数。

首先,它定义了要在数据库中插入的一对静态条目。请注意,这些只是普通的旧 JavaScript 对象;基本上,任何您可以表示为 JavaScript 对象的东西,您都可以存储在 MongoDB 中。接下来,它调用集合的insert方法,传递要存储的对象和一个回调函数,驱动程序在尝试插入后调用该函数。

驱动程序再次调用回调函数,传递一个错误值(在成功时为null)和作为它们被插入到集合中的 JavaScript 对象。我们的回调函数将结果日志记录到控制台,并调用回调插入函数的回调,关闭数据库。

插入的记录看起来是什么样子呢?以下是从我的控制台获取的示例,确保我们正在运行 MongoDB:

PS C:\Users\rarischp\Documents\Node.js\mongodb> node .\example.js
mongo.connect returned null
Inserted 2 documents with result:
[ { call: 'kf6gpe-7',
    lat: 37,
    lng: -122,
    _id: 54e2a0d0d00e5d240f22e0c0 },
  { call: 'kf6gpe-9',
    lat: 38,
    lng: -123,
    _id: 54e2a0d0d00e5d240f22e0c1 } ]

请注意,这些对象有相同的字段,但它们还有一个额外的_id字段,这是对象在数据库中的唯一标识符。在下一节中,您将学习如何针对该字段进行查询。

还有更多内容

如果你多次将同一个对象插入数据库,会发生什么?试试看!你会发现数据库中有该对象的多个副本;字段不用于指定唯一性(例外是_id字段,它在整个数据库中是唯一的)。注意你不能自己指定一个_id字段,除非您确信它是唯一的。要更新现有元素,请使用更新方法,我在本章的使用 Node.js 在 MongoDB 中更新文档菜谱中描述了该方法。

默认情况下,MongoDB 的插入操作很快,可能会失败(比如说,如果网络存在临时问题,或者服务器暂时过载)。为了保证安全,你可以将{ safe: true }作为插入操作的第二个参数,或者等待操作成功,或者在操作失败时返回一个错误。

也见

参考docs.mongodb.org/manual/reference/method/db.collection.insert/获取有关如何将文档插入 MongoDB 集合的文档。

使用 Node.js 在 MongoDB 中搜索文档

如果你不能搜索文档,那么能够插入文档也帮助不大。MongoDB 允许你指定一个模板进行匹配,并返回匹配该模板的对象。

与插入和更新操作一样,你将处理一个文档集合,调用集合的find方法。

如何做到...

这是一个例子,它找到 test 集合中所有kf6gpe-7的文档,并将它们打印到控制台:

var mongo = require('mongodb').MongoClient;

var url = 'mongodb://localhost:27017/test';

mongo.connect(url, function(error, db) {
  console.log("mongo.connect returned " + error);

  var cursor = collection.find({call: 'kf6gpe-7'});
  cursor.toArray(function(error, documents) {
    console.log(documents);

    db.close();
  });
});

它是如何工作的...

连接到数据库后,我们在集合中调用find,它返回一个游标,您可以使用它遍历找到的值。find方法接受一个 JavaScript 对象,作为模板指示您想要匹配的字段;我们的例子匹配名为call的字段等于kf6gpe-7的记录。

我们不是遍历游标,而是通过使用游标的toArray方法,将找到的所有值转换成一个单一的数组。这对于我们的例子来说是可以的,因为结果并不多,但是在具有很多项的数据库上这样做要小心!一次性从数据库中获取比你实际需要更多的数据,会使用到应该分配给应用程序其他部分的 RAM 和 CPU 资源。最好是遍历集合,或者使用分页,我们接下来会讨论。

还有更多内容

游标有几种方法可供您遍历搜索结果:

  • hasNext方法如果游标还有其他可以返回的项,则返回true。

  • next方法返回游标中的下一个匹配项。

  • forEach迭代器接收一个函数,按顺序对游标的每个结果调用该函数。

遍历游标时,最好使用带有hasNext的 while 循环并调用 next,或者使用forEach;不要只是将结果转换为数组并在列表上循环!这样做需要数据库一次性获取所有记录,可能会非常占用内存。

有时,可能仍然有太多的项目需要处理;您可以使用游标方法limit和skip来限制返回的条目数量。limit方法将搜索限制为您传递的参数数量的条目;skip方法跳过您指定的条目数量。

实际上,find 方法实际上接受两个参数:一个 JavaScript 对象是请求的准则,一个可选的 JavaScript 对象定义了结果集的投影,以新的 JavaScript 对象形式返回。

条件可以是精确匹配条件,正如你在上一个例子中看到的那样。你还可以使用特殊操作$gt和$lt进行匹配,这些操作允许你按基数顺序过滤给定字段。例如,你可能这样写:

var cursor = collection.find({lng: { $gt: 122 } });

这将返回所有lng字段值大于 122 的记录。

投影是一个你感兴趣的从数据库接收的字段列表,每个字段设置为true或1。例如,以下代码返回只包含call和_id字段的 JavaScript 对象:

var cursor = collection.find(
{call: 'kf6gpe-7'}, 
{call: 1, _id: 1});

参见

参见docs.mongodb.org/manual/reference/method/db.collection.find/关于 MongoDB find 方法的文档,该方法是原生驱动程序使 Node.js 应用程序可用的。

使用 Node.js 在 MongoDB 中更新文档

在集合中更新一个文档很容易;只需使用集合的update方法并传递您想要更新的数据。

如何做到…

这是一个简单的例子:

var mongo = require('mongodb').MongoClient;

var url = 'mongodb://localhost:27017/test';

var update = function(collection, callback) {
  collection.update({ call:'kf6gpe-7' }, 
    { $set: { lat: 39.0, lng: -121.0, another: true } }, 
    function(error, result) {
      console.log('Updated with error ' + error);
      console.log(result);
      callback(result);
    });
};

mongo.connect(url, function(error, db) {
  console.log("mongo.connect returned " + error);

  // Get the documents collection
  var collection = db.collection('documents');
  update(collection, function(result) {
    db.close();
  });
});

这个模式与insert方法相同;update是一个异步方法,它调用一个带有错误代码和结果的回调。

它是如何工作的…

update方法采用一个模板来匹配文档,并用传递给$set的 JavaScript 对象的值更新第一个匹配的文档。注意,你也可以向文档中添加新字段,就像我们在这里做的那样;我们添加了一个名为another的新字段,其值为true。

您可以通过传递文档的 ID 来指定与特定文档的精确匹配,该 ID 位于传递给 update 的模板的_id字段中。传递给update的模板是一个标准的查询模板,就像你会传递给find的那样。

还有更多…

默认情况下,update更新第一个匹配的文档。如果您想要它更新与您的模板匹配的所有文档,请在更新中传递一个可选的第三个参数,即 JavaScript 对象{ multi: true }。您还可以让update执行upsert,即在匹配成功时进行更新,如果匹配不成功则进行插入。为此,在更新的第三个参数中传递 JavaScript 对象{ upsert: true }。这些可以组合使用以匹配多个文档和执行 upsert;如果没有找到,则传递。

{
  multi: true,
  upsert: true
}

类似于插入操作,您还可以在这个选项的参数中传递safe: true,以确保在返回之前 update 尝试成功,但这样做会牺牲性能。

update方法将更新的文档数作为其结果传递给您的回调。

也见

参见 MongoDB 原生驱动程序文档中的 update 部分github.com/mongodb/node-mongodb-native或 MongoDB update 方法文档docs.mongodb.org/manual/reference/method/db.collection.update/。

使用 Node.js 在 MongoDB 中删除文档

在某个时候,您可能希望使用 Node.js 在集合中删除文档。

如何做到...

您使用remove方法来实现,该方法会从您指定的集合中移除匹配的文档。以下是调用remove方法的示例:

var remove = function(collection, callback) {
  collection.remove({ call: 'kf6gpe-7'},
    function(error, result)
    {
      console.log('remove returned ' + error);
      console.log(result);
      callback(result);
    });
};

如何工作…

这段代码移除了字段call值为kf6gpe-7的文档。正如您可能猜到的那样,remove的搜索条件可以是您会传递给 find 的任何东西。remove方法会移除所有与您的搜索条件匹配的文档,所以要小心!调用remove({})会移除当前集合中的所有文档。

remove方法返回从集合中删除的项目的数量。

也见

关于 MongoDB 的 remove 方法,请参阅其文档docs.mongodb.org/manual/reference/method/db.collection.remove/。

使用 REST 搜索 MongoDB

到目前为止,您可能想知道在使用 MongoDB 时 JSON 扮演什么角色。当您使用像 mongo-rest 这样的 RESTful 接口访问 MongoDB 数据库实例时,文档会使用 JSON 传输到客户端。让我们看看如何从 MongoDB 获取文档列表。

如何做到...

使用 Node.js、MongoDB 和 REST 需要几个步骤。

  1. 确保您已经按照介绍中的讨论设置了 REST 服务器。您需要创建rest-server.js、routes/documents.js和mongo-rest-example.html这些文件,其中包含我们 RESTful 应用的 UI,并用 Node.js 同时运行 REST 服务器和文档服务器。

  2. 其次,确保您正在运行 MongoDB。

  3. 接下来,为了处理 REST GET请求,我们需要在documents.js中定义函数exports.findAll,它应该如下所示:

    exports.findAll = function(req, res) {
      db.collection('documents', function(err, collection) {
        collection.find().toArray(function(err, items) {
          res.send(items);
        });
      });
    };
    
  4. 之后,我们需要mongo-rest-example.html文件中的doGet脚本,它对 REST 服务器上的数据库文档发起 AJAX GET请求。这段代码向服务器的/documents/ URL 发起 AJAX GET请求,将返回的 JSON 放入具有id为 json 的div中,并构建一个 HTML 表格,每个结果文档的结果有一行,提供每个文档的 ID、呼号、纬度和经度等列:

    function doGet() { 
      $.ajax({
        type: "GET",
        url: "http://localhost:3000/documents/",
        dataType: 'json',
      })
    .done(function(result) {
        $('#json').html(JSON.stringify(result));
        var resultHtml = 
    '<table><thead>' + 
    '<th><td><b>id</b></td><td><b>call</b></th>' + 
    '<tbody>';
        resultHtml += '<td><b>lat</b></td><td><b>lng</b></td></tr>';
    
          $.each(result), function(index, item)
          {
            resultHtml += '<tr>';
            resultHtml += '<td>' + item._id + '</td>';
            resultHtml += '<td>' + item.call + '</td>';
            resultHtml += '<td>' + item.lat + '</td>';
            resultHtml += '<td>' + item.lng + '</td>';
            resultHtml += "</tr>";
          };
        $resultHtml += '</tbody></table>';
    
        $('#result').html(resultHtml);
      })
    }
    

它是如何工作的…

findAll方法是对数据库的直接查询,它使用find在我们的集合中匹配所有的文档。你可以扩展它以接受一个查询模板作为 URL 参数,然后将该参数作为 URL 编码的参数传递给 GET URL。

你还可以添加其他参数,例如限制和跳过的参数,如果你处理的数据量很大,你应该考虑这样做。请注意,Express 模块知道它需要将 JavaScript 对象 JSON 编码以 JSON 的形式发送给客户端。

doGet JavaScript 代码更简单;它是一个纯粹的 AJAX 调用,后面跟着一个循环,将返回的 JSON 数组解包为对象,并将每个对象作为表格中的一行呈现。

还有更多

一个好的 REST 接口还提供了一个通过 ID 查询特定项目的接口,因为通常你希望查询集合,在其中找到一些有趣的内容,然后可能需要对这个特定的 ID 做些什么。我们定义了findById方法来接收来自 URL 的 ID,将 ID 转换为 MongoDB 对象id,然后仅对该 ID 执行find,如下所示:

exports.findById = function(req, res) {
  var id = new objectId(req.params.id);
  db.collection('documents', function(err, collection) {
    collection.findOne({'_id':id}, function(err, item) {
      res.send(item);
    });
  });
};

使用 REST 在 MongoDB 中创建文档

原则上,使用 REST 创建文档是简单的:在客户端创建 JavaScript 对象,将其编码为 JSON,并POST到服务器。让我们看看这个在实际中是如何工作的。

如何做到…

这有两部分:客户端部分和服务器部分。

  1. 在客户端,我们需要一种方式来获取我们新 MongoDB 文档的数据。在我们的例子中,它是 HTML 页面上的表单字段,我们将它们包装起来,并使用客户端(在 HTML 中)的doUpsert方法POST到服务器:

    function doUpsert(which)
    {
    Var id = $('#id').val();
    var value = {};
      value.call = $('#call').val();
      value.lat = $('#lat').val();
      value.lng = $('#lng').val();
    
      $('#debug').html(JSON.stringify(value));
    
    var reqType = which == 'insert' ? "POST" : 'PUT';
      var reqUrl = 'http://localhost:3000/documents/' + 
    (which == 'insert' ? '' : id);
    
      $.ajax({
        type: reqType,
        url: reqUrl,
        dataType: 'json',
        headers: { 'Content-Type' : 'application/json' },
        data: JSON.stringify(value)
      })
    .done(function(result) {
        $('#json').html(JSON.stringify(result));
    var resultHtml = which == 'insert' ? 'Inserted' : "Updated";
        $('#result').html(resultHtml);
      });
    }
    
  2. 服务器接受提交的文档,自动使用 body-parser 模块将其从 JSON 转换,并在 documents.js 文件中执行数据库插入:

    exports.addDocuments = function(req, res) {
      var documents = req.body;
      db.collection('documents', {safe:true}, 
    function(err, collection) {
    collection.insert(documents, function(err, result) {
     if (err) {
    res.send({'error':'An error has occurred'});
    } else {
     console.log('Success: ' + JSON.stringify(result[0]));
    res.send(result[0]);
            }
        });
     });
    };
    

它是如何工作的…

客户端代码被 UI 中的插入和更新按钮共同使用,这就是它比你可能最初想的要复杂一点的原因。然而,在 REST 中,插入和更新之间的唯一区别是 URL 和 HTTP 方法(POST与PUT),因此使用一个方法来处理两者是合理的。

客户端代码首先使用 jQuery 从表单中获取字段值,然后将请求类型设置为POST以进行更新。接下来,它构建 REST URL,这应该只是基本文档的 URL,因为新文档没有 ID。最后,它使用POST将文档的 JSON 发送到服务器。服务器代码很简单:取请求的一部分作为对象体,并将其插入到数据库的文档集合中,将插入的结果返回给客户端(这是一个很好的模式,以防客户端是新创建文档的 ID 用于任何事情)。

在服务器端,因为我们在使用 body-parser 模块的jsonParser实例注册POST请求的处理程序时,JSON 解码是自动处理的。

app.post('/documents', jsonParser, documents.addDocuments);

提示

如果你在路由注册时忘记传递 JSON 解析器,请求体字段甚至不会被定义!所以如果你在使用 Express 向数据库插入空文档,一定要检查这一点。

使用 REST 在 MongoDB 中更新文档

更新与插入相同,不同之处在于它需要一个文档 ID,并且客户端使用 HTTP POST请求而不是PUT请求来信号更新请求。

如何做到...

客户端代码与上一个食谱完全相同;只有服务器代码会更改,因为它需要从 URL 中提取 ID 并执行更新而不是插入:

exports.updateDocuments = function(req, res) {
  var id = new objectId(req.params.id);
  var document = req.body;
  db.collection('documents', function(err, collection) {
    collection.update({'_id':id}, document, {safe:true}, 
      function(err, result) {
        if (err) {
          console.log('Error updating documents: ' + err);
          res.send({'error':'An error has occurred'});
        } else {
          console.log('' + result + ' document(s) updated');
          res.send(documents);
        }
    });
  });
};

让我们更详细地看看。

它是如何工作的...

回到前面食谱中的客户端实现,你看到对于更新,我们在 URL 中包含了 ID。updateDocuments方法从请求参数中获取 ID,并将其转换为 MongoDB 对象id对象,然后调用update,客户端通过POST请求传递的文档。

使用 REST 在 MongoDB 中删除文档

与更新一样,删除需要一个对象id,我们将它在 URL 中传递给 HTTP DELETE请求。

如何做到...

doRemove方法从表单中的id字段获取对象id,并向由基本 URL 加上对象id组成的 URL 发送一个DELETE消息:

function doRemove()
{
  var id = $('#id').val();

  if(id == "")'') 
  {
    alert("Must provide an ID to delete!");
    return;
  }

  $.ajax({
    type: 'DELETE',
    url: "http://localhost:3000/documents/" + id  })
  .done(function(result) {
    $('#json').html(JSON.stringify(result));
    var resultHtml = "Deleted";
    $('#result').html(resultHtml);
  });
  }

服务器上的删除消息处理程序从 URL 中提取 ID,然后执行remove操作:

exports.deleteDocuments = function(req, res) {
  var id = new objectId(req.params.id);
  db.collection('documents', function(err, collection) {
    collection.remove({'_id':id}, {safe:true}, 
    function(err, result) {
      if (err) {
        res.send({'error':'An error has occurred - ' + err});
      } else {
        console.log('' + result + ' document(s) deleted');
        res.send({ result: 'ok' });
      }
    });
  });
};

它是如何工作的...

在客户端,流程与更新流程相似;我们从id表单元素中获取 ID,如果它是 null,它将弹出错误对话框而不是执行 AJAX post。我们使用 HTTP DELETE方法进行 AJAX post,在 URL 中将id作为文档名称传递给服务器。

在服务器端,我们从请求参数中获取 ID,将其转换为 MongoDB 本地对象 ID,然后将其传递给集合的remove方法以删除文档。然后将成功或错误返回给客户端。

第六章:使用 JSON 与 CouchDB 配合

在上一章中,我们研究了如何使用 JSON 与 MongoDB 配合,MongoDB 是一个流行的 NoSQL 数据库。在本章中,我们继续这一主题,向您展示如何使用 JSON 与 CouchDB 配合,CouchDB 又是另一个流行的 NoSQL 数据库。在这里,你会发现有关以下方面的食谱:

  • 安装和设置 CouchDB 和 Cradle

  • 使用 Node.js 和 Cradle 连接到 CouchDB 文档

  • 使用 Node.js 和 Cradle 创建 CouchDB 数据库

  • 使用 Node.js 和 Cradle 在 CouchDB 中创建文档

  • 使用 Node.js 和 Cradle 设置数据视图

  • 使用 Node.js 和 Cradle 在 CouchDB 中搜索文档

  • 使用 Node.js 和 Cradle 在 CouchDB 中更新文档

  • 使用 Node.js 和 Cradle 在 CouchDB 中删除文档

  • 使用 REST 枚举 CouchDB 记录

  • 使用 REST 搜索 CouchDB

  • 使用 REST 在 CouchDB 中更新或创建文档

  • 使用 REST 在 CouchDB 中删除文档

简介

CouchDB 是一个高可用性、可扩展的文档数据库。与 MongoDB 一样,它也是一个 NoSQL 数据库;不同的是,你不是将数据组织成通过 ID 相关联的表,而是将文档放入数据库中。与 MongoDB 不同,CouchDB 有一个有趣的特性,即 视图。

你将具有特定的 map 和 reduce 函数的文档放入数据库中,这些函数遍历数据以提供通过索引提供的特定数据视图。视图是缓存的,这使得构建高性能查询变得容易,这些查询返回数据子集或计算的数据(如报告)。

你与 CouchDB 交互的主要方式是通过 REST 接口;即使在本章中讨论的 Cradle 驱动程序,也是利用 REST 接口在幕后进行文档的创建、更新和删除。你还可以用 REST 接口进行查询,无论是通过文档 ID,还是将索引查询转换为视图。

在本章中,我们将研究如何使用 Cradle 模块将 CouchDB 与 Node.js 集成,以及如何从 Web 端对 CouchDB 进行 REST 查询。

安装和设置 CouchDB 和 Cradle

CouchDB 提供了主要平台的点击即可运行安装程序。

如何进行…

首先,你需要安装服务器。为此,请访问 couchdb.apache.org/ 并下载适合您平台的安装程序。在安装 Cradle 之前,一定要运行安装程序。

接下来,在命令行上运行以下命令来安装 Cradle:

npm install cradle

最后,你需要在 CouchDB 服务器上启用跨资源请求,以允许在 Web 上进行这些请求。为此,请编辑 /etc/couchdb/default.ini 文件,并更改以下行:

enable_cors = false

以下行:

enable_cors = true

你还需要指示你将接受 CORS 请求的哪些源服务器;要启用对所有域名的跨资源请求,请在 /etc/couchdb/default.ini 中 [cors] 部分添加以下行:

origins = *

如果你想要更具体一点,你可以提供一个由逗号分隔的域名列表,来自这些域名的 HTML 内容和脚本将被加载。

最后,你必须启动(或重新启动)CouchDB 服务器。在 Windows 上,假设你没有将其作为服务安装,就去你安装它的 bin 目录下运行 couchdb.bat;在 Linux 和 Mac OS X 上,杀死并重新启动 CouchDB 服务器进程。

它是如何工作的…

Cradle 模块是整合 CouchDB 和 Node.js 的流行方式,尽管如果你愿意,你也可以使用 Node.js 的 request 模块直接进行 REST 请求。

也见

关于 CouchDB 的更多信息,请参见 Apache CouchDB 维基百科上的页面:docs.couchdb.org/en/latest/contents.html。

使用 Node.js 和 Cradle 连接 CouchDB 数据库

尽管 CouchDB 提供了 RESTful 接口,但严格来说,在使用 CouchDB 之前并不需要一定要建立一个数据库连接;Cradle 模块使用连接的概念来管理其内部状态,你仍然需要创建一个连接对象。

怎样做到…

下面是如何在你的 Node.js 应用程序中包含 Cradle 模块并初始化它,获取对特定数据库的引用的方法:

var cradle = require('cradle');
var db = new(cradle.Connection)().database('documents');

它是如何工作的…

这段代码首先包含了 Cradle 模块,然后创建了一个新的 Cradle Connection 对象,将其数据库设置为 documents 数据库。这初始化了 Cradle,使其使用默认的 CouchDB 主机(localhost)和端口(5984)。如果你需要覆盖主机或端口,可以通过将主机和端口作为 Connection 构造函数的第一个和第二个参数来这样做,像这样:

var connection = new(cradle.Connection)('http://example.com', 
  1234);

使用 Node.js 和 Cradle 创建 CouchDB 数据库

在使用 CouchDB 中的数据库之前,你必须先创建它。

怎样做到…

一旦你获得了你想要使用的数据库的句柄,你应该检查它是否存在,如果不存在,则创建它:

db.exists(function (err, exists) {
if (err) {
  console.log('error', err);
} elseif (!exists) {
{
  db.create();
}
});

它是如何工作的…

exists 方法检查数据库是否存在,如果发生错误,调用你提供的回调函数,并带有一个指示数据库是否存在或不存在的标志。如果数据库不存在,你可以使用 create 方法来创建它。

这是 Cradle 的一个常见模式,因为 RESTful 接口本质上是非同步的。你会将你想要执行的方法的参数和回调函数传递给它。

提示

初学者常犯的一个错误是认为可以调用这些方法而不带回调函数,然后立即执行一些依赖于之前结果的操作。这是行不通的,因为原始操作还没有发生。考虑对同一记录进行插入和更新。插入是异步完成的;如果你尝试同步执行更新,将没有东西可以更新!

还有更多…

如果你想销毁一个数据库,你可以使用 destroy 方法,它也接受一个回调函数,就像 create 一样。这会销毁数据库中的所有记录,就像你想象的那么彻底,所以要小心使用!

使用 Node.js 和 Cradle 在 CouchDB 中创建文档

Cradle 模块提供了save方法来将新文档保存到数据库中。你传递要保存的文档和一个当操作完成或失败时调用的回调函数。

如何做到这一点...

下面是如何使用save保存一个简单记录的方法:

var item =  {
  call: 'kf6gpe-7',
  lat: 37,
  lng: -122
};

db.save(item, function (error, result) {
  if (error) {
    console.log(error);
    // Handle error
  } else {
    var id = result.id;
    var rev = result.rev;
    }
  });

它是如何工作的…

save方法返回一个 JavaScript 对象给你的回调函数,其中包含新创建文档的 ID 和一个内部修订号,以及一个名为 ok 的字段,该字段应该是 true。正如你在标题为《使用 Node.js 在 CouchDB 中更新记录》的食谱中看到的,为了更新一个文档,你需要存储文档的修订版和 ID;否则,你最终会创建一个新的文档或记录保存失败。一个示例结果可能看起来像这样:

{ ok: true,
  id: '80b20994ecdd307b188b11e223001e64',
  rev: '1-60ba89d42cc4bbc1301164a6ae5c3935' }

如何在 CouchDB 中使用 Node.js 和 Cradle 设置数据视图

你可以通过它们的 ID 查询 CouchDB 的文档,但当然,大多数时候,你会希望发出更复杂的查询,比如将记录中的字段与特定值匹配。CouchDB 允许你定义视图,这些视图由集合中的任意键和从视图中派生的对象组成。当你指定一个视图时,你是在指定两个 JavaScript 函数:一个map函数将键映射到集合中的项目,然后一个可选的reduce函数遍历键和值以创建最终集合。在本食谱中,我们将使用视图的map函数通过单个字段创建记录的索引。

如何做到这一点...

下面是使用 CouchDB 为数据库添加一个简单视图的方法:

db.save('_design/stations', {
  views: {
    byCall: {
      map: function(doc) {
        if (doc.call) {
          emit(doc.call, doc);
        }
      }
    }
  }
});

这为我们的数据库定义了一个单一视图,即byCall视图,它由一个呼号到数据库中文档的映射组成。

它是如何工作的…

视图是一种强大的方法,可以引用数据库中的文档,因为你可以根据数据库中的每个文档构建任意简单或复杂的文档。

我们的示例创建了一个单一视图byCall,存储在views目录下(你应该把视图放在这里),由每个记录的呼号字段组成,然后重复记录。CouchDB 定义了emit函数,让你为你的视图创建键值对;在这里,我们使用call字段作为每个值的关键字,文档本身作为值。你完全可以轻松地定义一个 JavaScript 对象中的字段子集,或者在 JavaScript 字段上计算某物,并发出那个东西。你可以定义多个视图,每个视图在views字段中是一个单独的map函数。

CouchDB 缓存视图,并根据数据库的变化按需更新它们,将视图数据存储为 B-树,因此在运行时更新和查询视图非常快。正如你在下一个示例中看到的,搜索特定键的视图简单到只需将键传递给视图。

视图在 CouchDB 中只是存储在特定位置的文档,使用函数而不是数据值。内部实现上,CouchDB 在存储视图时编译视图的函数,并在存储发生插入和删除等更改时运行它们。

也请参阅

使用 Node.js 和 Cradle 在 CouchDB 中搜索文档

在 CouchDB 中搜索文档就是查询特定视图以获取特定键的问题。Cradle 模块定义了view函数来实现这一点。

如何进行...

您将传递要执行查询的视图的 URL,然后将您正在搜索的键作为键参数传递,像这样:

var call = "kf6gpe-7";
db.view('stations/byCall/key="' + call + '"', 
  function (error, result) {
    if (result) {
      result.forEach(function (row) {
        console.log(row);
});

除了传递您所寻找的视图和键外,您必须传递一个处理结果的回调函数。

它是如何工作的…

在这里,我们在byCall视图中搜索调用信号为kf6gpe-7。回想一下上一个食谱,视图由call字段中的调用信号映射到记录组成;当我们使用数据库的view方法发出视图请求时,它在那个映射中查找键匹配kf6gpe-7的记录,并返回由匹配记录组成的数组结果。该方法使用数组的forEach方法遍历数组中的每个元素,一次将每个元素写入控制台。

还有更多内容

您可以向视图传递多个参数。最明显的是key参数,它让您传递一个键以进行匹配。还有keys参数,它让您传递一个键的数组。您还可以传递startkey和endkey,以查询一个键范围的视图。如果您需要限制结果,您可以使用limit和skip参数来限制结果数量,或跳过前n个匹配的结果。

如果您知道一个文档的 ID,您还可以使用 Cradle 的get方法直接获取该对象:

db.get(id, function(error, doc) {
  console.log(doc);
});

也请参阅

关于您可以对视图执行的查询操作的详细信息,请参阅 CouchDB 维基百科中的wiki.apache.org/couchdb/HTTP_view_API#Querying_Options。

使用 Node.js 和 Cradle 在 CouchDB 中更新文档

Cradle 模块定义了merge方法,以便让您更新现有文档。

如何进行...

以下是一个示例,我们通过指定其 ID 将记录的调用从kf6gpe-7更改为kf6gpe-9,然后使用新数据执行合并:

var call = "kf6gpe-7";

db.merge(id, {call: 'kf6gpe-9'}, function(error, doc) {
  db.get(id, function(error, doc) {
    console.log(doc);
  });
});

从函数中,你可以看到merge接收要合并记录的 ID 和一个 JavaScript 对象,该对象包含要替换或添加到现有对象的字段。你还可以传递一个回调函数,当合并操作完成时由 merge 调用。在出错的情况下,错误值将为非零,文档作为第二个参数返回。在这里,我们只是将修订后的文档的内容记录到控制台。

使用 Node.js 和 Cradle 在 CouchDB 中删除文档

要删除一个记录,你使用 Cradle 模块的remove方法,并传递你想要删除的文档的 ID。

如何进行...

下面是一个删除记录的例子:

db.remove(id);

通过 ID 删除文档会移除具有指定 ID 的文档。

还有更多...

如果你有多个文档要删除,你可以像以下代码那样遍历所有文档,逐一删除每个文档:

db.all(function(err, doc) {
  for(var i = 0; i < doc.length; i++) {
    db.remove(doc[i].id, doc[i].value.rev, function(err, doc) {
      console.log('Removing ' + doc._id);
    });
  }
});

这是remove的一个更复杂的使用方式;它需要文档的 ID、文档的版本以及一个回调函数,该函数会将每个被移除文档的 ID 记录到控制台。

使用 REST 枚举 CouchDB 记录

REST 语义规定,要获取对象集合的完整内容,我们只需向集合的根发送一个GET请求。我们可以从启用了 CORS 的 CouchDB 中使用 jQuery 用一个调用完成这个操作。

如何进行...

这里有一些 HTML、jQuery 和 JavaScript 代码,它枚举了 CouchDB 视图中的所有项目,并在内嵌表格中显示了每个对象的一些字段:

<!DOCTYPE html>
<html>
<head>
<script src="img/"></script>
<script src="img/"></script>
</head>
<body>

<p>Hello world</p>
<p>
  <div id="debug"></div>
</p>
<p>
  <div id="json"></div>
</p>
<p>
  <div id="result"></div>
</p>

<button type="button" id="get" onclick="doGet()">Get</button><br/>
<form>
  Id: <input type="text" id="id"/>
  Rev: <input type="text" id="rev"/>
  Call: <input type="text" id="call"/>
  Lat: <input type="text" id="lat"/>
  Lng: <input type="text" id="lng"/>
  <button type="button" id="insert" 
    onClick="doUpsert('insert')">Insert</button>
  <button type="button" id="update" 
    onClick="doUpsert('update')">Update</button>
  <button type="button" id="remove" 
    onClick="doRemove()">Remove</button>
</form><br/>

<script>

function doGet() { 
  $.ajax({
    type: "GET",
    url: 
"http://localhost:5984/documents/_design/stations/_view/byCall",
    dataType:"json",
  })
  .done(function(result) {
    $('#json').html(JSON.stringify(result));
    var resultHtml = '<table><tr><td><b>id</b></td>';
    resultHtml += '<td><b>revision</b></td><td><b>call</b></td>';
    resultHtml += '<td><b>lat</b></td><td><b>lng</b></td></tr>';
    for(var i = 0; i < result.rows.length; i++)
    {
      var item = result.rows[i]
      resultHtml += "<tr>";
      resultHtml += "<td>" + item.id + "</td>";
      resultHtml += "<td>" + item.value._rev + "</td>";
      resultHtml += "<td>" + item.value.call + "</td>";
      resultHtml += "<td>" + item.value.lat + "</td>";
      resultHtml += "<td>" + item.value.lng + "</td>";
      resultHtml += "</tr>";
    }
    $('#result').html(resultHtml);
});
}
</script>
</html>

它是如何工作的…

HTML 结构很简单;它包含了 jQuery,然后定义了三个div区域来显示请求的结果。之后,它定义了一个表单,包含文档的 ID、版本、呼号、纬度和经度字段,并添加了获取记录列表、执行插入或更新以及移除记录的按钮。

我们需要定义byCall视图才能使其工作(参见食谱使用 Node.js 在 CouchDB 中设置数据视图,了解如何使用 Node.js 设置数据视图)。这段代码对视图的基本 URL 执行一个 HTTP GET 请求,并取回的 JavaScript 对象(由 jQuery 从 JSON 解析而来)进行格式化,使其成为一个表格。(注意我们本可以附加一个特定的键到 URL 上,以获取单一的 URL)。

REST 响应的格式与使用 Cradle 查询集合的响应略有不同;你看到的是 CouchDB 的实际响应,而不是由 Cradle 处理的成果。以原始形式来看,它看起来像这样:

{"total_rows":1,"offset":0,
  "rows":[
    {"id":"80b20994ecdd307b188b11e223001e64",
"key":"kf6gpe-7",
      "value":{
"_id":"80b20994ecdd307b188b11e223001e64",
"_rev":"1-60ba89d42cc4bbc1301164a6ae5c3935",
"call":"kf6gpe-7","lat":37,"lng":-122
      }
    }
  ]
} 

具体来说,total_rows字段表示集合中结果有多少行;offset字段表示在返回的第一行之前在集合中跳过了多少行,然后rows数组包含了映射视图生成的每个键值对。rows字段有一个 ID 字段,它是生成该映射条目的唯一 ID,由映射操作生成的键,以及由映射操作生成的记录。

请注意,如果你对数据库的基本 URL 执行一个GET请求,你会得到一些不同的事物;不是数据库中的所有记录,而是有关数据库的信息:

{"db_name":"documents",
"doc_count":5,
"doc_del_count":33,
"update_seq":96,
"purge_seq":0,
"compact_running":false,
"disk_size":196712,
"data_size":6587,
"instance_start_time":"1425000784214001",
"disk_format_version":6,
"committed_update_seq":96
}

这些字段可能因您运行的 CouchDB 版本而异。

参见

有关 CouchDB 的 HTTP REST 接口的信息,请参阅位于wiki.apache.org/couchdb/HTTP_Document_API的文档。

使用 REST 搜索 CouchDB

使用 REST 搜索 CouchDB 时,使用一个带有映射的视图来创建你的索引,你插入一次,然后是一个 GET HTTP 请求。

如何做到...

我们可以修改之前的doGet函数,以搜索特定的呼号,如下所示:

function doGet(call) { 
  $.ajax({
    type: "GET",
    url: 
"http://localhost:5984/documents/_design/stations/_view/byCall" + 
       (call != null & call != '') ? ( '?key=' + call ) : '' ),
    dataType:"json",
  })
  .done(function(result) {
    $('#json').html(JSON.stringify(result));
    var resultHtml = '<table><tr><td><b>id</b></td>';
    resultHtml += '<td><b>revision</b></td><td><b>call</b></td>';
    resultHtml += '<td><b>lat</b></td><td><b>lng</b></td></tr>';
    for(var i = 0; i < result.rows.length; i++)
    {
      var item = result.rows[i]
      resultHtml += "<tr>";
      resultHtml += "<td>" + item.id + "</td>";
      resultHtml += "<td>" + item.value._rev + "</td>";
      resultHtml += "<td>" + item.value.call + "</td>";
      resultHtml += "<td>" + item.value.lat + "</td>";
      resultHtml += "<td>" + item.value.lng + "</td>";
      resultHtml += "</tr>";
    }
    $('#result').html(resultHtml);
  });
}

它是如何工作的…

相关的行是传递给doGet的参数调用,以及我们构造的 URL,我们通过GET请求发送到该 URL。注意我们如何检查 null 或空调用以获取整个集合;你的代码可能希望做些不同的事情,比如报告一个错误,特别是如果集合很大的话。

提示

请注意,视图必须在这样做之前存在。我喜欢使用 Node.js 在最初更新我的数据库时创建我的视图,并在更改时更新视图,而不是将视图嵌入客户端,因为对于大多数应用程序来说,有很多客户端,没有必要让存储重复更新相同的视图。

使用 REST 在 CouchDB 中更新或插入文档

当你想要执行一个更新或插入操作时,Cradle 并没有 REST 等效的合并功能;相反,插入操作由 HTTP POST请求处理,而更新操作则由PUT请求处理。

如何做到...

以下是一些 HTML 和一个doUpsert方法,它查看你 HTML 页面上的表单元素,如果数据库中尚不存在文档,则创建新文档,或者如果已存在文档并且你传递了 ID 和修订字段,则更新现有文档:

<!DOCTYPE html>
<html>
<head>
<script src="img/"></script>
<script src="img/"></script>
</head>
<body>

<p>Hello world</p>
<p>
  <div id="debug"></div>
</p>
<p>
  <div id="json"></div>
</p>
<p>
  <div id="result"></div>
</p>

<button type="button" id="get" onclick="doGet()">Get</button><br/>
<form>
  Id: <input type="text" id="id"/>
  Rev: <input type="text" id="rev"/>
  Call: <input type="text" id="call"/>
  Lat: <input type="text" id="lat"/>
  Lng: <input type="text" id="lng"/>
  <button type="button" id="insert" 
    onClick="doUpsert('insert')">Insert</button>
  <button type="button" id="update" 
    onClick="doUpsert('update')">Update</button>
  <button type="button" id="remove" 
    onClick="doRemove()">Remove</button>
</form><br/>

<script>

function doUpsert();
{	
  var value = {};
  var which = null;
  id = $('#id').val();

  if (id != '') {
    which = 'insert';
  }

  value.call = $('#call').val();
  value.lat = $('#lat').val();
  value.lng = $('#lng').val();

  if (which != 'insert') {
    value._rev = $('#rev').val();
    value._id = id;
  }

  $('#debug').html(JSON.stringify(value));

  var reqType = which == 'insert' ? "POST" : "PUT";
  var reqUrl = "http://localhost:5984/documents/" + 
    (which == 'insert' ? '' : id);

  $.ajax({
    type: reqType,
    url: reqUrl,
    dataType:"json",
    headers: { 'Content-Type' : 'application/json' },
    data: JSON.stringify(value)
  })
  .done(function(result) {
    $('#json').html(JSON.stringify(result));
    var resultHtml = which == 'insert' ? "Inserted" : "Updated";
    $('#result').html(resultHtml);
  })
}
</script>
</html>

它是如何工作的…

doUpsert方法首先定义一个空 JavaScript 对象,这是我们将其填充并通过PUT或POST请求发送到服务器的对象。然后我们提取表单字段的值;如果id字段设置了 ID,我们假设这是更新操作,而不是插入操作,并且还捕获了名为rev的修订字段的值。

如果没有设置 ID 值,它是一个插入操作,我们将请求类型设置为POST。如果它是更新,我们将请求类型设置为PUT,向 CouchDB 表明这是一个更新。

接下来,我们构造 URL;更新文档的 URL 必须包括要更新的文档的 ID;这就是 CouchDB 知道要更新哪个文档的方式。

最后,我们执行一个我们之前定义类型的 AJAX 请求(PUT或POST)。当然,我们将发送给服务器的 JavaScript 文档进行 JSON 编码,并包含一个指示发送的文档是 JSON 的头部。

返回的值是一个 JSON 文档(由 jQuery 转换为 JavaScript 对象),包括插入文档的 ID 和修订版,类似于这样:

{ "ok":true,
  "id":"80b20994ecdd307b188b11e223001e64",
  "rev":"2-e7b2a85adef5e721634bdf9a5707eb42"}

提示

请注意,您更新文档的请求必须包括文档的当前修订版和 ID,否则PUT请求将因 HTTP 409 错误而失败。

使用 REST 在 CouchDB 中删除文档

您通过向要删除的文档发送带有 ID 和修订版的 HTTP DELETE请求来表示 RESTful 删除。

如何做到…

使用之前的食谱中的 HTML,这是一个脚本,它从表单字段中提取 ID 和修订版,进行一些简单的错误检查,并向服务器发送具有指示 ID 和修订版的文档的删除请求:

function doRemove()
{	
  id = $('#id').val();
  rev = $('#rev').val();
  if (id == '') 
  {
    alert("Must provide an ID to delete!");
    return;
  }
  if (rev == '')
  {
    alert("Must provide a document revision!");
    return;
  }

  $.ajax({
    type: "DELETE",
    url: "http://localhost:5984/documents/" + id + '?rev=' + rev,
  })
  .done(function(result) {
    $('#json').html(JSON.stringify(result));
    var resultHtml = "Deleted";
    $('#result').html(resultHtml);
  })
}

它是如何工作的…

代码首先从表单元素中提取 ID 和修订版,如果任何一个为空则弹出错误对话框。接下来,构建一个 AJAX HTTP DELETE请求。URL 是文档的 URL - 数据库和文档 ID,修订版作为名为rev的参数传递。假设您正确指定了 ID 和修订版,您将得到与更新相同的响应:被删除文档的 ID 和修订版。 如果失败,您将得到一个 HTTP 错误。

第七章.以类型安全的方式使用 JSON

在本章中,我们将在第一章,在客户端读写 JSON的食谱基础上,向您展示如何使用 C#、Java 和 TypeScript 在您的应用程序中使用强类型。您将找到以下食谱:

  • 如何使用 Json.NET 反序列化对象

  • 如何使用 Json.NET 处理日期和时间对象

  • 如何使用 gson 为 Java 反序列化对象

  • 如何使用 Node.js 与 TypeScript

  • 如何使用 TypeScript 注解简单类型

  • 如何使用 TypeScript 声明接口

  • 如何使用 TypeScript 声明带有接口的类

  • 使用 json2ts 从您的 JSON 生成 TypeScript 接口

简介

有些人说强类型是弱智的标志,但事实是,编程语言中的强类型可以帮助你避免一整类错误,其中你错误地假设一个对象实际上属于另一种类型。像 C#和 Java 这样的语言提供强类型正是出于这个原因。

幸运的是,C#和 Java 的 JSON 序列化器支持强类型,一旦您弄清楚了对象表示,只想将 JSON 映射到您已经定义的类的实例时,这尤其方便。在第一章中,在客户端读写 JSON,您看到了如何将 C#或 Java 类转换为 JSON,以及如何将 JSON 转换为未命名的对象;在本章中,我们使用 Json.NET 对 C#和 gson 对 Java 将 JSON 转换为您应用程序中定义的类的实例。

最后,我们来看看 TypeScript,这是 JavaScript 的一个扩展,提供了类型在编译时的检查,编译成普通的 JavaScript 以供与 Node.js 和浏览器一起使用。我们将查看如何为 Node.js 安装 TypeScript 编译器,如何使用 TypeScript 注解类型和接口,以及如何使用 Timmy Kokke 的网页自动从 JSON 对象生成 TypeScript 接口。

如何使用 Json.NET 反序列化对象

在本食谱中,我们将向您展示如何使用 Newtonsoft 的 Json.NET 将 JSON 反序列化为类的实例。我们将使用 Json.NET,这是我们在第一章,在客户端读写 JSON中提到的,因为尽管这适用于现有的.NET JSON 序列化器,但我还想要您了解关于 Json.NET 的其他内容,我们将在接下来的两个食谱中讨论。

准备阶段

首先,您需要确保您的项目中有一个对 Json.NET 的引用。最简单的方法是使用 NuGet;启动 NuGet,搜索 Json.NET,然后点击安装,如下面的屏幕截图所示:

准备阶段

你还需要在需要这些类的任何文件中,在文件的顶部使用using指令引用Newonsoft.Json命名空间:

usingNewtonsoft.Json;

如何做到…

下面是一个示例,提供了简单类的实现,将 JSON 字符串转换为此类的实例,然后将实例转换回 JSON:

using System;
usingNewtonsoft.Json;

namespaceJSONExample
{

  public class Record
  {
    public string call;
    public double lat;
    public double lng;
  }

  class Program
  {
    static void Main(string[] args)
      {
        String json = @"{ 'call': 'kf6gpe-9', 
        'lat': 21.9749, 'lng': 159.3686 }";

        var result = JsonConvert.DeserializeObject<Record>(
          json, newJsonSerializerSettings
            {
        MissingMemberHandling = MissingMemberHandling.Error
          });
        Console.Write(JsonConvert.SerializeObject(result));

        return;
        }
  }
}

如何工作…

为了以类型安全的方式反序列化 JSON,我们需要有一个与我们的 JSON 具有相同字段的类。在第一行定义的Record类这样做,定义了call、lat和lng字段。

Newtonsoft.Json命名空间提供了JsonConvert类,带有静态方法SerializeObject和DeserializeObject。DeserializeObject是一个泛型方法,接受应返回的对象的类型作为类型参数,以及 JSON 解析的 JSON 和可选参数指示 JSON 解析的选项。我们传递MissingMemberHandling属性作为设置,用枚举值Error表示,如果字段缺失,解析器应抛出异常。在解析类之后,我们再次将其转换为 JSON,并将结果 JSON 写入控制台。

还有更多…

如果你跳过传递MissingMember选项或传递Ignore(默认值),你可以在 JSON 中的字段名与你的类之间存在不匹配,这可能不是你进行类型安全转换所想要的。你还可以传递NullValueHandling字段,其值为Include或Ignore。如果为Include,包含具有空值的字段;如果为Ignore,则忽略具有空值的字段。

请参阅

Json.NET 的完整文档在www.newtonsoft.com/json/help/html/Introduction.htm。

使用.NET 序列化器也可以进行类型安全的 JSON 支持;语法相似。有关示例,请参阅JavaScriptSerializer 类的文档。

使用 Json.NET 处理日期和时间对象

JSON 中的日期对人们来说是个问题,因为 JavaScript 的日期是从纪元开始以来的毫秒数,这通常对人们来说是难以阅读的。不同的 JSON 解析器处理方式不同;Json.NET 有一个很好的IsoDateTimeConverter,它将日期和时间格式化为 ISO 格式,使得在其他平台(除了 JavaScript)上进行调试或解析时人类可读。你也可以通过创建新的转换器对象并使用转换器对象将一个值类型转换为另一个值类型,将此方法扩展到转换 JSON 属性中的任何格式化数据。

如何做到…

只需在调用JsonConvert.Serialize时包含一个新的IsoDateTimeConverter对象,像这样:

string json = JsonConvert.SerializeObject(p, 
newIsoDateTimeConverter());

如何工作…

这导致序列器调用IsoDateTimeConverter实例,以任何日期和时间对象实例化,返回如下的 ISO 字符串:

2015-07-29T08:00:00

还有更多…

请注意,这可以被 Json.NET 解析,但不是 JavaScript;在 JavaScript 中,您希望使用像这样的函数:

Function isoDateReviver(value) {
  if (typeof value === 'string') {
  var a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(?:([\+-])(\d{2})\:(\d{2}))?Z?$/
  .exec(value);
  if (a) {
     var utcMilliseconds = Date.UTC(+a[1], 
          +a[2] - 1, 
          +a[3], 
          +a[4], 
          +a[5], 
          +a[6]);
        return new Date(utcMilliseconds);
    }
  }
return value;
}

第三行的相当复杂的正则表达式匹配 ISO 格式的日期,提取每个字段。如果正则表达式找到匹配项,它将提取每个日期字段,然后使用Date类的 UTC 方法创建新的日期。

提示

请注意,整个正则表达式——/字符之间的所有内容——应该位于同一行,且没有空格。然而,这个页面有点长!

另见

关于 Json.NET 如何处理日期和时间的更多信息,请参阅www.newtonsoft.com/json/help/html/SerializeDateFormatHandling.htm上的文档和示例。

使用 gson 为 Java 反序列化对象

与 Json.NET 一样,gson 提供了一种指定您要反序列化的 JSON 对象目标类的方法。实际上,这正是您在第一章客户端的 JSON 读写中使用的食谱读写 JSON中使用的相同方法。

准备中

您需要将 gson JAR 文件包含在您的应用程序中,就像任何其他外部 API 一样。

如何做到…

您使用的方法与使用 gson 进行类型不安全的 JSON 解析时使用的fromJson方法相同,只是您将类对象作为第二个参数传递给 gson,像这样:

// Assuming we have a class Record that looks like this:
/*
class Record {
  private String call;
  private float lat;
  private float lng;
    // public API would access these fields
}
*/

Gson gson = new com.google.gson.Gson(); 
String json = "{ \"call\": \"kf6gpe-9\", 
\"lat\": 21.9749, \"lng\": 159.3686 }";
Record result = gson.fromJson(json, Record.class);

如何工作…

fromGson方法总是接受一个 Java 类;在第一章客户端的 JSON 读写中,我们要反序列化的类是JsonElement,它处理 JSON 的一般动态性。在本食谱的示例中,我们直接转换为一个简单的 Java 对象,我们的应用程序可以使用,而无需使用 gson 提供的JsonElement的反引用和类型转换接口。

还有更多…

gson 库也可以处理嵌套类型和数组。您还可以通过将字段声明为transient来隐藏字段,使其不被序列化或反序列化,这是有意义的,因为瞬态字段不会被序列化。

另见

gson 及其支持反序列化类实例的文档在sites.google.com/site/gson/gson-user-guide#TOC-Object-Examples。

如何使用 TypeScript 与 Node.js

使用 TypeScript 与 Visual Studio 配合使用很容易;它是 Visual Studio 2013 Update 2 之后的任何版本的 Visual Studio 安装的一部分。为 Node.js 获取 TypeScript 编译器同样简单——只需一个npm install。

如何做到…

在带有npm的命令行中,运行以下命令:

npm install –g typescript

npm选项–g告诉npm将 TypeScript 编译器全局安装,这样它就可以供你写的每一个 Node.js 应用程序使用了。一旦你运行这个命令,npm就会下载并为你所在的平台安装 TypeScript 编译器的二进制文件。

更多内容…

一旦你运行这个命令来安装编译器,你就可以在命令行上使用 TypeScript 编译器tsc了。用tsc编译一个文件和写源代码并保存为一个以.ts结尾的文件一样简单,然后在该文件上运行tsc。例如,假设以下 TypeScript 代码保存在名为hello.ts的文件中:

function greeter(person: string) {
  return "Hello, " + person;
}

var user: string = "Ray";

console.log(greeter(user));

在命令行运行tschello.ts会生成以下的 JavaScript 代码:

function greeter(person) {
  return "Hello, " + person;
}

var user = "Ray";

console.log(greeter(user));

试试看!

正如我们在下一节所看到的,greeter的函数声明包含了一个 TypeScript 注解;它声明参数person为string。在hello.ts的底部添加以下一行:

console.log(greeter(2));

现在,再次运行tschello.ts命令;你会得到一个错误,像这样的一个:

C:\Users\rarischp\Documents\node.js\typescript\hello.ts(8,13): error TS2082: Supplied parameters do not match any signature of call target:
        Could not apply type 'string' to argument 1 which is of type 'number'.
C:\Users\rarischp\Documents\node.js\typescript\hello.ts(8,13): error TS2087: Could not select overload for 'call' expression.

这个错误表明我试图用错误类型的值调用greeter,传了一个数字给期望字符串的greeter。在下一个菜谱中,我们将查看 TypeScript 支持为简单类型提供的哪些类型注解。

参见 also

TypeScript 的官方网站,包括教程和参考文档,位于www.typescriptlang.org/。

如何使用 TypeScript 注解简单类型

TypeScript 中的类型注解是简单地附加在变量或函数后面的冒号和装饰器。支持与 JavaScript 相同的原始类型,以及我们接下来要讨论的声明接口和类。

如何做到…

以下是一个简单的变量声明和两个函数声明的例子:

function greeter(person: string): string {
  return "Hello, " + person;
}

function circumference(radius: number) : number {
  var pi: number = 3.141592654;
  return 2 * pi * radius;
}

var user: string = "Ray";

console.log(greeter(user));
console.log("You need " + 
circumference(2) + 
  " meters of fence for your dog.");

这个例子展示了如何注解函数和变量。

它是如何工作的…

变量——作为独立变量或函数参数——使用冒号后跟类型进行装饰。例如,第一个函数greeter接受一个参数person,必须是字符串。第二个函数circumference接受一个半径,必须是数字,并在其作用域中声明了一个变量pi,必须是数字并且有值3.141592654。

你像在 JavaScript 中一样以正常方式声明函数,然后在函数名后面加上类型注解,再次使用冒号和类型。所以,greeter返回一个字符串,circumference返回一个数字。

更多内容…

TypeScript 定义了以下基本类型装饰器,它们映射到其底层的 JavaScript 类型:

  • array:这是一个复合类型。例如,你可以像下面这样写一个字符串列表:

    var list:string[] = [ "one", "two", "three"];
    
  • boolean:这个类型装饰器可以包含true和false这两个值。

  • number:这个类型装饰器类似于 JavaScript 本身,可以是任何浮点数。

  • string:这个类型装饰器是字符串。

  • enum:枚举,使用enum关键字编写,像这样:

    enumColor { Red = 1, Green, Blue };
    var c : Color = Color.Blue;
    
  • any:这个类型表示变量可以是任何类型。

  • void:这个类型表示值没有类型。你将使用void来表示一个不返回任何内容的函数。

参见

要查看 TypeScript 类型的列表,请参阅 TypeScript 手册中的TypeScript 类型。

如何使用 TypeScript 声明接口

接口定义了事物的行为,而没有定义实现。在 TypeScript 中,接口通过描述它所拥有的字段来命名一个复杂类型。这被称为结构子类型化。

如何做到…

声明接口有点像声明一个结构或类;你在接口中定义字段,每个字段都有自己的类型,像这样:

interface Record {
  call: string;
  lat: number;
  lng: number;
}

Function printLocation(r: Record) {
  console.log(r.call + ': ' + r.lat + ', ' + r.lng);
}

var myObj = {call: 'kf6gpe-7', lat: 21.9749, lng: 159.3686};

printLocation(myObj);

它是如何工作的…

在 TypeScript 中,interface关键字定义了一个接口;如我前面所提到的,接口包含它声明的字段和它们的类型。在这个列表中,我定义了一个普通的 JavaScript 对象myObj,然后调用了我之前定义的接受一个Record的函数printLocation。当用myObj调用printLocation时,TypeScript 编译器检查字段和类型,只有当对象符合接口时,才允许调用printLocation。

还有更多…

小心!TypeScript 只能提供编译时类型检查。你认为下面的代码会做什么呢?

interface Record {
  call: string;
  lat: number;
  lng: number;
}

Function printLocation(r: Record) {
  console.log(r.call + ': ' + r.lat + ', ' + r.lng);
}

var myObj = {call: 'kf6gpe-7', lat: 21.9749, lng: 159.3686};
printLocation(myObj);

var json = '{"call":"kf6gpe-7","lat":21.9749}';
var myOtherObj = JSON.parse(json);
printLocation(myOtherObj);

首先,这个代码用tsc编译是没有问题的。当你用 node 运行它时,你会看到以下内容:

kf6gpe-7: 21.9749, 159.3686
kf6gpe-7: 21.9749, undefined

发生了什么?TypeScript 编译器不会为你的代码添加运行时类型检查,所以你不能对一个非字面创建的运行时对象强加一个接口。在这个例子中,因为 JSON 中缺少了lng字段,函数无法打印它,而是打印了undefined的值。

这并不意味着你不应该使用 TypeScript 与 JSON 一起使用,然而。类型注解对所有代码的读者都有用,无论是编译器还是人。你可以使用类型注解来表明你作为开发者的意图,并且代码的读者可以更好地理解你所写的代码的设计和限制。

参见

关于接口的更多信息,请参阅 TypeScript 文档中的接口部分。

如何使用 TypeScript 声明带有接口的类

接口让你可以指定行为而不指定实现;类让你可以将实现细节封装在一个接口后面。TypeScript 类可以封装字段或方法,就像其他语言中的类一样。

如何做到…

下面是一个我们的记录结构示例,这次作为一个带有接口的类:

class RecordInterface {
  call: string;
  lat: number;
  lng: number;

  constructor(c: string, la: number, lo: number) {}
  printLocation() {}

}

class Record implements RecordInterface {
  call: string;
  lat: number;
  lng: number;

  constructor(c: string, la: number, lo: number) {
    this.call = c;
    this.lat = la;
    this.lng = lo;
  }

  printLocation() {
    console.log(this.call + ': ' + this.lat + ', ' + this.lng);
  }
}

var myObj : Record = new Record('kf6gpe-7', 21.9749, 159.3686);

myObj.printLocation();

它是如何工作的…

再次,interface关键字定义了一个接口,正如前一部分所展示的。你之前没见过的class关键字实现了一个类;可选的implements关键字表明这个类实现了接口RecordInterface。

请注意,实现接口的类必须具有与接口规定的相同的所有字段和方法;否则,它不符合接口的要求。因此,我们的Record类包括了call、lat和lng字段,类型与接口中的相同,以及构造方法和printLocation方法。

构造方法是一种特殊的方法,当你使用new创建类的新实例时会被调用。请注意,与常规对象不同,创建类的正确方式是使用构造函数,而不是仅仅将它们构建为字段和值的集合。我们在列表的倒数第二行这样做,将构造函数参数作为函数参数传递给类构造函数。

参见

你可以用类做很多事情,包括定义继承和创建公有和私有的字段和方法。关于 TypeScript 中类的更多信息,请参阅www.typescriptlang.org/Handbook#classes的文档。

使用 json2ts 从你的 JSON 生成 TypeScript 接口

这个最后的食谱更像是一个提示而不是一个食谱;如果你有一些使用其他编程语言开发或手工编写的 JSON,你可以通过使用 Timmy Kokke 的 json2ts 网站轻松地为包含 JSON 的对象创建一个 TypeScript 接口。

如何做到…

只需访问json2ts.com,将你的 JSON 代码粘贴到出现的文本框中,然后点击生成 TypeScript 按钮。你会看到一个新文本框出现,展示了 TypeScript 接口的定义,你可以将这个定义保存为一个文件,并在你的 TypeScript 应用程序中包含它。

它是如何工作的…

下面的图表展示了一个简单的例子:

它是如何工作的…

你可以将这个 TypeScript 保存为一个自己的文件,一个definition文件,后缀为.d.ts,然后使用import关键字包含模块,像这样:

import module = require('module');

第八章:使用 JSON 进行二进制数据传输

在本章中,我们将讨论 JSON 和二进制数据之间的交集。在这里,您会找到以下菜谱:

  • 使用 Node.js 将二进制数据编码为 base64 字符串

  • 使用 Node.js 从 base64 字符串解码二进制数据

  • 在浏览器中使用 JavaScript 将二进制数据编码为 base64 字符串

  • 使用 Json.NET 将数据编码为 BSON

  • 使用 Json.NET 解码 BSON 数据

  • 使用DataView访问ArrayBuffer

  • 使用ArrayBuffer进行 base64 的编码和解码

  • 使用 express 模块构建的 Node.js 服务器上压缩对象体内容

引言

使用 JSON 时考虑二进制表示通常有两个原因:要么是因为你需要将在应用程序的一个部分与另一个部分之间传输二进制数据,要么是因为你担心传输的 JSON 数据的大小。

在第一种情况下,你实际上有点束手无策,因为现有的 JSON 规范没有为二进制数据提供容器格式,因为 JSON 在本质上是一种基于文本的数据表示。你可以选择将二进制数据编码为另一种格式,如 base64,将二进制数据表示为可打印的字符串,或者使用支持二进制数据的 JSON 扩展,如二进制 JSON(BSON)。

BSON 使用 JSON 的语义,但以二进制形式表示数据。因此,同样的基本结构是可用的:一个(可能嵌套的)键值对映射,其中值可以是其他键值对、数组、字符串,甚至是二进制数据。然而,代替使用纯文本编码,该格式是二进制的,这产生了更小的数据大小并支持原生二进制对象(您可以在bsonspec.org/了解更多关于 BSON 的信息)。BSON 的缺点是它不是原生支持 JavaScript,而且作为一种二进制格式,不容易进行检查。为了激发你的兴趣,我将在本章讨论如何使用流行的 Json.NET 库与 BSON 一起使用。

第二个方法是取任何二进制数据,并将其编码为与文本兼容的格式。Base64 就是这样一种编码机制,多年来在互联网上用于各种目的,并且在现代浏览器和 Node.js 中都有支持。在本章中,我展示了使用现代浏览器接口和 Node.js 与 base64 相互转换的菜谱。请注意,这意味着数据膨胀,因为将二进制信息表示为文本会增加传输的数据大小。

人们在考虑为他们的应用程序使用 JSON 时经常表达的一个担忧是,JSON 包的大小与二进制格式(如 BSON、协议缓冲区或手工调优的二进制表示)相比。虽然 JSON 可能比二进制表示大,但您获得了可读性(特别有助于调试)、清晰的语义,以及大量可用的库和实施实例。减少空白字符和使用简短的关键字名称可以帮助减小 JSON 的大小,压缩也可以——在我最近的一个项目中,我的测试显示,使用标准的 HTTP 压缩对 JSON 进行压缩,比全部二进制表示节省的内存更多,当然在服务器和客户端实现起来也更简单。

请记住,为了节省内存而转换为二进制格式——无论是 BSON、压缩还是自定义格式——都会抵消 JSON 的一个最有用的属性,即其自文档化属性。

使用 Node.js 将二进制数据编码为 base64 字符串

如果您有二进制数据需要编码以作为 JSON 传递给客户端,您可以将其转换为 base64,这是在互联网上表示八位值的一种常见方式,仅使用可打印字符。Node.js 提供了Buffer对象和base64编码器和解码器来完成这项任务。

如何做到…

首先,您会分配一个缓冲区,然后将其转换为字符串,指示您想要的字符串应该是 base64 编码的,如下所示:

var buffer = newBuffer('Hello world');
var string = buffer.toString('base64');

它是如何工作的…

Node.js的Buffer类包装了一组八位字节,位于 Node.js V8 运行时堆之外。当您需要在 Node.js 中处理纯二进制数据时,它会用到。我们示例的第一行创建了一个缓冲区,用字符串Hello world填充它。

Buffer类包含toString方法,该方法接受一个参数,即编码缓冲区的手段。这里,我们传递了base64,表示我们希望s包含b的base64表示,但我们可以同样容易地传递以下值之一:

  • ascii:这个值表示应该移除高位比特,并将每个八位字节剩余的 7 位转换为其 ASCII 等效值。

  • utf8:这个值表示它应该作为多字节 Unicode 编码。

  • utf16le:这些是 2 个或 4 个字节的小端 Unicode 字符。

  • hex:这个值是将每个八位字节编码为两个字符,八位字节的hex值。

也见

有关 Node.js 的Buffer类的文档,请参阅nodejs.org/api/buffer.html。

从 base64 字符串解码二进制数据使用 Node.js

在 Node.js 中,没有Buffer.toString的逆操作;相反,您直接将 base64 数据传递给缓冲区构造函数,并附上一个标志,表示数据是 base64 编码的。

准备

如果你想要像这里显示的那样运行示例,你需要安装buffertools模块,以获取Buffer.compare方法。为了获得这个模块,请在命令提示符下运行npm:

npm install buffertools

如果你只是要使用 Node.js 的Buffer构造函数来解码 base64 数据,你不需要做这个。

如何做到…

在这里,我们将我们的原始缓冲区与另一个用原始 base64 初始化的缓冲区进行比较,这是为了第一个消息:

require('buffertools').extend();

var buffer = new Buffer('Hello world');
var string = buffer.toString('base64');
console.log(string);

var another = new Buffer('SGVsbG8gd29ybGQ=', 'base64');
console.log(b.compare(another) == 0);

它是如何工作的…

代码的第一行包含了buffertools模块,它扩展了Buffer接口。这只是为了在最后一行使用缓冲区工具的Buffer.compare方法,不是因为 base64 需要自我解码。

接下来的两行创建了一个Buffer对象并获取其base64表示,接下来的行将这个表示输出到控制台。

最后,我创建了第二个Buffer对象,用一些 base64 数据初始化它,传递 base64 以表示初始化数据应该被解码到缓冲区中。我在最后一行比较这两个缓冲区。注意,缓冲区工具的compare方法是一个序数比较,意味着如果两个缓冲区包含相同的数据,它返回 0,如果第一个包含小于数据的序数排序,它返回-1,如果第一个包含序数排序更大的数据,它返回 1。

也见

关于buffertools模块及其实现的信息,请参阅github.com/bnoordhuis/node-buffertools#。

在浏览器中使用 JavaScript 对二进制数据进行 base64 字符串编码

JavaScript 的基本实现不包括 base64 编码或解码。然而,所有现代浏览器都包括了atob和btoa方法来分别解码和编码 base64 数据。这些方法是 window 对象的方法,由 JavaScript 运行时定义。

如何做到…

这只是方法调用的简单:

var encodedData = window.btoa("Hello world"); 
var decodedData = window.atob(encodedData);

它是如何工作的…

btoa函数接收一个字符串并返回该字符串的 base64 编码。它是 window 对象的方法,并调用原生浏览器代码。atob函数做相反的事情,接收一个包含 base64 的字符串并返回一个包含二进制数据的字符串。

也见

关于btoa和atob的总结,请参阅 Mozilla 开发者网站上的developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding(注意虽然这些文档来自 Mozilla,但这些window的方法由大多数现代浏览器定义)。

使用 Json.NET 对数据进行 BSON 编码

BSON 编码是如果你在连接的每一边都有一个编码器和解码器的实现,那么它是 JSON 的一个合理替代方案。不幸的是,目前还没有适合 JavaScript 的好编码器和解码器,但是有包括.NET 和 C++在内的许多其他平台上的实现。让我们看看如何使用 Json.NET 在 C#中对 BSON 进行编码。

准备开始

首先,你需要让你的应用程序能够访问 Json.NET 程序集。正如你在上一章中看到的,在食谱如何使用 Json.NET 反序列化一个对象中,最容易的方法是使用 NuGet。如果你还没有这么做,按照那个食谱的步骤将 Json.NET 程序集添加到你的解决方案中。

如何做到…

使用 Json.NET 来编码 BSON 相对简单,一旦你有了想要编码的类:

public class Record {
  public string Callsign { get; set; }
  public double Lat { get; set; }
  public double Lng { get; set; }
} 
…
var r = new Record {
  Callsign = "kf6gpe-7",
  Lat = 37.047,
  Lng = 122.0325
};

var stream = new MemoryStream();
using (var writer = new Newtonsoft.Json.Bson.BsonWriter(ms))
{
  var serializer = new Newonsoft.Json.JsonSerializer();
  serializer.Serialize(writer, r);
}

它是如何工作的…

最容易的方法是从一个具有你想要转换的场的类开始,正如你为其他类型的 JSON 安全转换所做的那样。在这里,我们为了这个目的定义了一个简单的Record类,然后创建一个记录来编码。

接下来,我们创建一个MemoryStream来包含编码后的数据,以及一个BsonWriter对象来将数据写入内存流。当然,任何实现.NET 流接口的东西都可以与BsonWriter实例一起使用;如果你愿意,你可以写入文件而不是内存流。在那之后,我们创建一个实际的序列化器来完成工作,JsonSerializer的一个实例,并使用它本身来序列化我们使用编写器创建的记录。我们将实际的序列化包裹在一个 using 块中,这样在操作结束时,写入器使用的资源(但不是流)会立即被.NET 运行时清理。

参见 also

关于 BsonWriter 类的文档可以从 NewtonSoft 处获得,网址为www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Bson_BsonWriter.htm。

使用 Json.NET 从 BSON 中解码数据

使用 Json.NET,解码 BSON 与编码相反;给定一个描述要解码数据的类和一个二进制数据块,调用一个读取器来读取数据。

准备开始

当然,为了做到这一点,你需要在你项目中有一个 Json.NET 程序集的引用。参见第七章使用类型安全的方式使用 JSON中的食谱如何使用 Json.NET 反序列化一个对象,了解如何使用 NuGet 在你的应用程序中添加 Json.NET 的引用。

如何做到…

从一个流开始,你将使用BsonReader和JsonSerializer来反序列化 BSON。假设数据是 BSON 数据的byte[]:

MemoryStream ms = new MemoryStream(data);
using (var reader = new Newtonsoft.Json.Bson.BsonReader(ms))
{
  var serializer = new Newtonsoft.Json.JsonSerializer();
  var r = serializer.Deserialize<Record>(reader);

  // use r
}

它是如何工作的…

我们从传入的数据中创建MemoryStream,然后使用BsonReader实际从流中读取数据。读取工作由JsonSerializer完成,它使用读取器将数据反序列化为Record类的新实例。

还有更多…

你可能没有代表反序列化数据的类的应用;这在开发初期很常见,当时你仍在定义数据传输的语义。你可以使用Deserialize方法反序列化一个JsonObject实例,然后使用JsonObject的接口获取各个字段值。关于JsonObject的信息,请参阅 Json.NET 文档www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonObjectAttribute.htm。

参见

BsonReader的文档在www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Bson_BsonReader.htm。

使用DataView访问ArrayBuffer

有时,你不想与 JSON 一起工作,而是想与纯二进制数据一起工作。JavaScript 提供了DataView抽象,它让你可以在一个数组缓冲区的内存上进行类型化的访问,比如从一个XMLHttpRequest对象获得的内存。

准备中

开始之前,你需要你的数据在一个ArrayBuffer中,比如XMLHttpRequest对象返回的那个。有了这个,你可以创建一个DataView,然后使用那个DataArray,在数据视图上创建一个类型数组,以提取你感兴趣的字节。让我们看一个例子。

如何做到…

这是一个简单的示例:

var req = new XMLHttpRequest();
req.open("GET", url, true);
req.responseType = "arraybuffer";
req.onreadystatechange = function () {
  if (req.readyState == req.DONE) {
    var arrayResponse = req.response;
    var dataView = new DataView(arrayResponse);
    var ints = new Uint32Array(dataView.byteLength / 4);

    // process each int in ints here.

    }
}
req.send();

它是如何工作的…

首先要注意到的是XMLHttpRequest对象的responseType。在这个例子中,我们将它设置为arraybuffer,表示我们想要一个以ArrayBuffer类实例表示的原始字节缓冲区。我们发起请求,在完成处理程序上创建DataView的响应。

DataView是一个抽象对象,从这个对象中我们可以创建不同的视图来读写ArrayBuffer对象中的二进制数据。

DataView支持将ArrayBuffer对象视为以下内容:

  • Int8Array: 这是一个 8 位补码有符号整数数组

  • Uint8Array: 这是一个 8 位无符号整数数组

  • Int16Array: 这是一个 16 位补码有符号整数数组

  • Uint16Array: 这是一个 16 位无符号整数数组

  • Int32Array: 这是一个 32 位补码有符号整数数组

  • Uint32Array: 这是一个 32 位无符号整数数组

  • Float32Array: 这是一个 32 位浮点数数组

  • Float64Array: 这是一个 64 位浮点数数组

除了从一个DataView构造这些数组之外,你还可以从一个DataView访问单个 8 位、16 位、32 位整数或 32 位或 64 位浮点数,使用相应的获取函数,传递你想获取的偏移量。例如,getInt8返回指定位置的Int8,而getFloat64获取你指定偏移量处的相应的 64 位浮点数。

参见

尽管 ArrayBuffer 和 DataView 并不仅限于 Microsoft Internet Explorer,但 Microsoft 的 MSDN 网站上的文档非常清晰。有关 DataView 方法的信息,请参阅 msdn.microsoft.com/en-us/library/br212463(v=vs.94).aspx,或者参见 msdn.microsoft.com/library/br212485(v=vs.94).aspx 以获取关于类型数组的概述。

使用 ArrayBuffer 进行 base64 编码和解码

如果你打算使用 ArrayBuffer 和 DataView 为你 的二进制数据,并将二进制数据作为 base64 字符串携带,你可以使用由 Mozilla 编写的函数,位于 developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#Solution_.232_.E2.80.93_rewriting_atob%28%29_and_btoa%28%29_using_TypedArrays_and_UTF-8 进行如此操作。他们提供了 strToUTF8Arr 和 UTF8ArrToStr 函数来执行 UTF-8 编码和解码,以及 base64EncArr 和 base64DecToArr 函数来在 base64 字符串和数组缓冲区之间进行转换。

如何做到…

这是一个相互转换示例,它将文本字符串编码为 UTF-8,然后将文本转换为 base64,然后显示 base64 结果,最后将 base64 转换为 UTF-8 数据的 ArrayBuffer,然后再将 UTF-8 转换回普通字符串:

var input = "Base 64 example";

var inputAsUTF8 = strToUTF8Arr(input);

var base64 = base64EncArr(inputAsUTF8);

alert(base64);

var outputAsUTF8 = base64DecToArr(base64);

var output = UTF8ArrToStr(outputAsUTF8);

alert(output);

它是如何工作的…

Mozilla 在他们的网站文件中定义了四个函数:

  • base64EncArr 函数将字节 ArrayBuffer 编码为 base64 字符串

  • base64DecToArr 函数将 base64 字符串解码为字节 ArrayBuffer

  • strToUTF8Arr 函数将字符串编码为 ArrayBuffer 中的 UTF-8 编码字符数组

  • UTF8ArrToStr 函数接受 ArrayBuffer 中的 UTF-8 编码字符数组,并返回它所编码的字符串

压缩 Node.js 服务器中使用 express 模块构建的对象体内容

如果你在使用 JSON 时有空间方面的主要考虑,让你在考虑二进制表示时,你应该认真考虑使用压缩。压缩可以带来与二进制表示相似的节省,在大多数服务器和 HTTP 客户端中使用 gzip 实现,并且可以在调试完你的应用程序后作为透明层添加。在这里,我们讨论为流行的基于 Node.js 的 express 服务器发送的 JSON 和其他对象添加对象体压缩。

准备好了

首先,你需要确保已经安装了 express 和 compress 模块:

npm install express
npm install compression

如果你想要它在你的工作区中的所有 Node.js 应用程序中可用,你也可以 npm install –g 它。

如何做到…

在你服务器的入口点初始化 express 模块时,需要 require 压缩,并告诉 express 使用它:

var express = require('express')
var compression = require('compression')
var app = express()
app.use(compression())

// further express setup goes here.

关于如何使用express模块来设置服务器的更多信息,请参阅第五章中的菜谱“为 Node.js 安装 express 模块”,使用 JSON 与 MongoDB.

它是如何工作的…

HTTP 头支持客户端指示它是否能够解压缩通过 HTTP 发送的对象体,并且现代浏览器都接受gzipped对象体。通过在基于 express 的服务器中包含 compress,你使得客户端可以请求压缩后的 JSON 作为其 Web API 请求的一部分,并且响应中也返回压缩后的 JSON。在大多数情况下,大多数客户端不需要进行任何更改,尽管如果你正在编写带有自己 HTTP 实现的本地客户端,你可能需要查阅文档以确定如何通过 HTTP 启用gzip解压缩。

代码首先需要引入 express 模块和压缩模块,然后配置 express 模块,在客户端请求压缩时,可选地使用压缩功能来发送响应。

第九章:使用 JSONPath 和 LINQ 查询 JSON

有时,您可能只想从一些 JSON 格式的数据中提取一两个字段,而不是将 JSON 块解析为一个类并处理其所有字段。使用 JSONPath 或 LINQ(使用 Json.NET),您可以做到这一点。在这里,您会找到以下食谱:

  • 使用 JSONPath 点表示法查询 JSON 文档

  • 使用 JSONPath 方括号表示法查询 JSON 文档

  • 使用 JSONPath 脚本构建更复杂的查询

  • 在您的 Web 应用程序中使用 JSONPath

  • 在您的 Node.js 应用程序中使用 JSONPath

  • 在您的 PHP 应用程序中使用 JSONPath

  • 在您的 Python 应用程序中使用 JSONPath

  • 在您的 Java 应用程序中使用 JSONPath

  • 使用 SelectToken 在您的 C#应用程序中查询 JSONPath 表达式

  • 使用 Json.NET 和 LINQ 在您的 C#应用程序中查询 JSON

引言

XML 最大的优点之一是 XPath,它是一种查询语言,用于查询 XML 文档的子部分。Stefan Goessner 提出了 JSONPath 查询语言,这是一种与 XPath 类似具有相似特性的语言,可以让您提取 JSON 文档中应用程序需要的部分。

请注意,仍然有人在解析东西:没有免费的午餐,JSONPath 实现需要至少具有相似内存和运行时特性的 JSON 解析。然而,如果您的平台上有 JSONPath 库,那么 JSONPath 可以使代码更易读,因为您不需要模拟整个类仅仅是为了提取一个或两个字段,或者是在一系列 JSON 值中对某个字段进行汇总。

如果您习惯于为 Microsoft 平台开发,您肯定知道 Microsoft 的语言独立查询(LINQ)语言,它允许您对可枚举数据结构编写声明式查询。尽管.NET 实现的 JSON 解析只提供基本的 LINQ 支持,但不屈不挠的 Json.NET 库的实现支持 LINQ 以及 JSONPath,让您可以使用流畅的或语句语法对 JSON 文档进行声明式查询。

要使用 JSONPath 或 LINQ,您需要一个支持它们的库。在我写这篇文章的时候,有支持 JavaScript 的 JSONPath 库,支持 Node.js 的 JavaScript,以及支持 PHP、C#、Python 和 Java 的库。当然,如果您想要使用 LINQ,您需要在.NET 平台上运行您的应用程序,使用 C#、F#或 Visual Basic 等语言。因此,接下来的大多数食谱都有两个步骤:第一步是下载一个支持 JSONPath 的库,然后是实际在应用程序中调用 JSONPath 代码的步骤。

大多数 JSONPath 示例都使用了 Goessner 的示例文档,该文档包含了假设书店的记录,在本章中,我们将同样使用这个示例。我们的 JSON 文档看起来是这样的:

{ "store": {
    "book": [ 
        { "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
        { "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
        { "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
        { "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}

正如您所看到的,我们有一个商店对象,它有一个书籍集合和一个单一的自行车。每本书都有一个类别、一个作者、一个标题和一个价格。像这样表示 JSON 文档作为一个类将很困难,因为书籍记录的结构与自行车记录的结构非常不同;您可以使用我们在第一章和第二章中讨论的不安全查询方法来解析这样的文档并遍历它的文档,尽管对于大多数应用程序来说,JSONPath 是一个更好的选择,您很快就会看到。让我们从如何查询文档中的单个字段开始。

使用 JSONPath 点表示法查询 JSON 文档

JSONPath 使用点表示法或方括号表示法编写表达式,以表示 JSON 文档中的字段遍历。点分隔字段名称,就像它们是对象属性一样。

如何做到这一点…

以下是一些点表示法的示例:

$.store.book[0].title
$.store.book[*].title
$.store..price
$..book[3]

它是如何工作的…

在第一行中,我们引用了商店中的第一个书籍(从零开始计数),返回标题字段。第二行类似,只不过它返回了所有书籍的标题集合。第三个例子返回了商店集合中所有记录的价格字段集合。第四个例子找到了商店中的第四本书项。

该表示法相当直观,除了 .. 和 * 的使用。这些都是 JSONPath 用来表示文档中切片的一些特殊字符的例子。

还有更多…

JSONPath 定义了以下特殊字符,您在编写查询时可以使用:

  • $ 符号指的是根对象或元素。

  • @ 符号指的是当前对象或元素。

  • . 操作符是点子操作符,您使用它来指定当前元素的子元素。

  • [] 操作符是下标操作符,您使用它来指定当前元素的子元素(按名称或索引)。

  • * 操作符是一个通配符,返回所有对象或元素,而不管它们的名称是什么。

  • , 操作符是并集操作符,它返回所指示的子项或索引的并集。

  • : 操作符是数组切片操作符,因此您可以使用语法 [start:end:step] 来返回集合的一个子集。

  • () 操作符允许您在底层实现的脚本语言中传递一个脚本表达式。然而,并非所有的 JSONPath 实现都支持它。

也见

JSONPath 的权威文档可以在 Goessner 的网站上找到,网址为 goessner.net/articles/JsonPath/。当然,您应该查看您选择的 JSONPath 的文档,以了解特定的实现细节。

网络上的一个方便的功能是一个 JSONPath 表达式测试器;jsonpath.curiousconcept.com/就是这样一个网站。通过在测试器中粘贴 JSON 和 JSONPath 表达式,你可以评估 JSONPath 并查看结果是什么。这是你在最初开始时动态调试你的 JSONPath 表达式的一个非常简单的方法。这是一个例子:

参见

使用 JSONPath 方括号表示法查询 JSON 文档

JSONPath 提供了一种替代表示法,即方括号表示法,它与点表示法一样用于查询字段。这个语法让你想起了如何访问关联数组中的字段,你只需要将字段名作为选择器传递给operator[],以获取命名字段中的值。

如何做到…

在方括号表示法中,我们将之前的例子的公式写作如下:

$['store']['book'][0].['title']
$['store']['book'][*].['title']
$['store']..['price']
$..['book'][3]

它是如何工作的…

如前所见,第一个例子提取了对象中名为 store 的字段中的第一个书籍的标题。第二个例子提取了商店中所有书籍的标题。第三个例子返回了商店中每个项目的所有价格字段的一个集合,第四个例子返回了商店中的第四本书。

使用 JSONPath 脚本构建更复杂的查询

有时候,你真正想做的事情是查询满足某些条件的所有项目,比如那些超过特定阈值的项目。JSONPath 提供了?()谓词,它可以让你执行 JSONPath 中单个字段的简单比较脚本。

如何做到…

这是一个查询所有价格低于10货币单位的书籍的例子:

$.store.book[?(@.price < 10)].title

它是如何工作的…

查询从指定商店中的所有书籍项目开始;?()谓词然后使用@选择器获取当前项目的值,然后选择价格低于10的项目。最后提取结果项目的标题字段。这个查询产生了以下结果:

[ 
    "Sayings of the Century",
    "Moby Dick"
]

这样的查询并不是所有 JSONPath 实现都能工作的。在jsonpath.curiousconcept.com/检查 JSONPath 表达式测试器,我发现它使用 flow communications JSONPath 0.1.1 工作,但 Goessner 在版本 0.8.3 中的 JSONPath 实现不能工作。

任何返回布尔值的表达式都可以用在?()谓词中。这是一个查询我们集合中所有小说类别的书籍的另一个例子:

$.store.book[?(@.category == "fiction")].title

开始的部分是一样的,那就是选择所有书籍;不是通过价格筛选并返回价格低于10的书籍,而是返回集合中某个特定书籍集合中的特定项目类别字段等于小说的所有项目。

在您的网络应用程序中使用 JSONPath

在您的网络应用程序中使用 JSONPath 与 JavaScript 结合是非常简单的。你只需要在你的应用程序中包含jsonpath.js实现,然后使用它的jsonPath函数。

准备

在开始之前,你需要从code.google.com/p/jsonpath/下载 JavaScript jsonpath库,并使用 script 标签将其包含在 HTML 页面使用的脚本中,像这样:

<html>
<head>
<title>…</title>
<script type="text/javascript" src="img/jsonpath.js"></script>
</head>

jsonPath函数接受一个 JSON 对象(不是作为字符串,而是作为 JavaScript 对象)并对内容应用路径操作,返回匹配的值或规范化的路径。让我们看一个例子。

如何做到…

以下是一个示例,它从我在引言中展示的 JSON 对象中返回一个标题列表:

var o = { /* object from the introduction */ };
var result = jsonPath(o, "$..title");

请注意,如果你有对象作为字符串,你将需要首先使用JSON.parse解析它:

var json = "…";
var o = JSON.parse(json);
var result = jsonPath(o, "$..title");

如何工作…

前面的代码使用jsonPath函数从当前传递的对象中提取所有标题。jsonPath函数接受一个 JavaScript 对象、路径和一个可选的结果类型,指示返回值应该是值还是值的路径。当然,传入的对象可以是结构化对象或数组。

也见

Goessner 关于 JSONPath 原始实现的原始文档在goessner.net/articles/JsonPath/。

在你的 Node.js 应用程序中使用 JSONPath

有一个 npm 包可用,其中包含 JavaScript JSONPath 实现的实现,所以如果你想在 Node.js 中使用 JSONPath,你只需要安装 JSONPath 模块并直接调用它。

准备好了

要安装JSONPath模块,运行以下命令将模块包含在你的当前应用程序中:

npm install JSONPath

或者,你可以运行以下命令将其包括在你系统上的所有项目中:

npm install –g JSONPath

接下来,你需要在你的源代码中要求模块,像这样:

var jsonPath = require('JSONPath');

这将JSONPath模块加载到你的环境中,并将引用存储在jsonPath变量中。

如何做到…

Node.js 的 JSONPath 模块定义了一个单一的方法eval,该方法接受一个 JavaScript 对象和一个要评估的路径。例如,为了获得我们示例文档中的标题列表,我们需要执行以下代码:

var jsonPath = require('JSONPath');

var o = { /* object from the introduction */ };
var result = jsonPath.eval(o, "$..title");

如果你打算将路径应用于以字符串形式的 JSON,请确保先解析它:

var jsonPath = require('JSONPath');

var json = "…";
var o = JSON.parse(json);
var result = jsonPath.eval(o, "$..title");

如何工作…

JSONPath模块的eval方法接受一个 JavaScript 对象(不是包含 JSON 的字符串)并应用你传递的路径以返回对象中的对应值。

也见

关于 Node.js 的 JSONPath 模块的文档,请参阅www.npmjs.com/package/JSONPath。

在你的 PHP 应用程序中使用 JSONPath

在你的 PHP 应用程序中使用 JSONPath 需要你包含位于code.google.com/p/jsonpath/的 JSONPath PHP 实现,并在应用你想要用jsonPath函数从其中提取数据的 JSONPath 路径之前,将 JSON 字符串解析为 PHP 混合对象。

准备好了

你需要从code.google.com下载jsonpath.php,位于code.google.com/p/jsonpath/,并通过require_once指令将其包含在你的应用程序中。你还需要确保你的 PHP 实现包括了json_decode。

如何做到…

这是一个简单的例子:

<html>
<body>
<pre>
<?php
  require_once('jsonpath.php');
  $json = '…'; // from the introduction to this chapter
  $object = json_decode($json);
  $titles = jsonPath($object, "$..title");
  print($titles);
?>
</pre>
</body>
</html>

它是如何工作的…

上述代码首先引入了 PHP JSONPath 实现,该实现定义了jsonPath函数。然后使用json_decode解码 JSON 字符串,再提取json_decode返回的混合 PHP 对象中的标题。

与 JavaScript 版本的jsonPath类似,PHP 版本接受三个参数:要执行提取的对象、要提取的路径以及一个可选的第三个参数,用于指定是返回数据还是返回数据结构的路径。

也见

有关 PHP 实现 JSONPath 的更多信息,请参阅 Stefan Goessner 的网站goessner.net/articles/JsonPath/。

在你的 Python 应用程序中使用 JSONPath

也为 Python 提供了几个 JSONPath 实现。最好的是jsonpath-rw库,它提供了语言扩展,使路径成为一等语言对象。

准备中

你需要使用 pip 安装jsonpath-rw库:

pip install jsonpath-rw

当然,你还需要在使用它们时包含库所需的部分:

fromjsonpath_rw import jsonpath, parse

如何做到…

这是一个简单的例子,使用存储在变量object中的介绍中的商店内容:

>>> object = { … }

>>>path = parse('$..title') 

>>> [match.value for match in path.find(object)]
['Sayings of the Century','Sword of Honour', 'Moby Dick', 'The Lord of the Rings']

它是如何工作的…

使用这个库处理路径表达式有点像匹配正则表达式;你解析出 JSONPath 表达式,然后使用路径的find方法将其应用于你想要切片的 Python 对象。这段代码定义了对象,然后创建了一个路径表达式将其存储在路径中,解析获取所有标题的 JSONPath。最后,在传递给路径的你要切片的对象中创建了一个由路径找到的值的数组。

也见

Python JSONPath 库的文档位于pypi.python.org/pypi/jsonpath-rw。

在你的 Java 应用程序中使用 JSONPath

还有一个为 Java 编写的 JSONPath 实现,由Jayway编写。它可以从 GitHub 获取,或者如果你的项目使用 Maven 构建系统,你还可以通过中央 Maven 仓库获取。它与原始 JSONPath API 相匹配,为 JSON 对象中的字段返回 Java 对象和集合。

准备中

你需要从 GitHub 上下载代码github.com/jayway/JsonPath,或者,如果你使用 Maven 作为你的构建系统,请包含以下依赖项:

<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.0.0</version>
</dependency>

如何做到…

Java 实现解析你的 JSON,并导出一个JsonPath类,该类有一个read方法,用于读取 JSON,解析它,然后提取你传递的路径的内容:

String json = "...";

List<String>titles = JsonPath.read(json,
"$.store.book[*].title");

它是如何工作的…

读取方法解析传递给它的 JSON,然后将传递给它的路径应用于提取 JSON 中的值。如果你需要从同一文档中提取多个路径,最好只解析文档一次,然后对解析后的文档调用读取方法,如下所示:

String json = "...";

Object document = 
Configuration.defaultCConfiguration().jsonProvider().parse(json));

List<String>titles = JsonPath.read(document,
  "$.store.book[*].title");
List<String>authors = JsonPath.read(document,
  "$.store.book[*].author");

还有更多…

Java JSONPath 库还提供了一种流畅的语法,其中读取和其他方法的实现返回一个上下文,你可以继续调用其他 JSONPath 库方法。例如,为了获取价格超过10的书籍列表,我还可以执行以下代码:

List<Map<String, Object>>expensiveBooks = JsonPath
                            .using(configuration)
                            .parse(json)
                            .read("$.store.book[?(@.price > 10)]", 
                              List.class);

这配置了JsonPath,使用了配置,解析了传递给它的 JSON,然后使用路径选择器调用read,选择所有价格超过值10的书籍对象。

Java 中的 JsonPath 库试图将结果对象转换为你期望的基本类:列表、字符串等等。一些路径操作—..、?()和[number:number:number]—总是返回一个列表,即使结果值是一个单一的对象。

参见

关于 Java JSONPath 实现的文档,请参阅github.com/jayway/JsonPath。

使用 JSONPath 和 SelectToken 在 C#应用程序中查询 JSONPath 表达式

如果你使用的是.NET 环境中的 Newonsoft Json.NET,你可以使用其SelectToken实现对 JSON 文档进行 JSONPath 查询。首先,你需要将 JSON 解析为JObject,然后进行查询。

准备

你需要在你的应用程序中包含 Json.NET 库。为此,请按照如何使用 Json.NET 反序列化对象食谱中的准备部分中的第七章使用类型安全的 JSON中的步骤操作。

如何进行…

以下是提取所有书籍标题的步骤以及得到的第一结果:

using System;
using System.Collections.Generic;
using System.Linq;
   using Newtonsoft.Json.Linq;

// …

static void Main(string[] args)
{
  var obj = JObject.Parse(json);

  var titles = obj.SelectTokens("$.store.book[*].title");

  Console.WriteLine(titles.First());
} 

它是如何工作的…

JObject的SelectTokens方法接受一个 JSONPath 表达式,并将其应用于对象。在这里,我们提取匹配顶级$.store.book路径的每个项目的JObject实例列表,然后调用Values方法获取每个返回的JObject实例中的每个标题字段的强制字符串值。当然,原始 JSON 需要解析,我们使用JObject.parse进行解析。

请注意SelectTokens返回一个可枚举的集合,你可以使用 LINQ 表达式进一步处理,就像我们在这里通过调用First一样。严格来说,SelectTokens返回IEnumberable<JToken>,其中每个JToken都是单个 JSON 集合。JObject 还提供SelectToken方法,返回一个实例。

然而,要小心不要混淆SelectToken和SelectTokens。前者只能返回一个JToken,而后者在你想要返回 JSONPath 查询中的项目集合时是必需的。

也支持过滤。例如,为了获得包含关于书籍Moby Dick的数据的JObject,我可能会写:

var book = obj.SelectToken(
"$.store.book[?(@.title == 'Moby Dick')]");

此选择从store字段中的book集合中选择标题匹配"Moby Dick"的文档。

请也参阅

参阅 Jason Newton-King 网站上关于SelectToken和SelectTokens的文档和更多示例,网址为james.newtonking.com/archive/2014/02/01/json-net-6-0-release-1-%E2%80%93-jsonpath-and-f-support,或者 Json.NET 文档www.newtonsoft.com/json/help/html/QueryJsonSelectToken.htm。

使用 Json.NET 和 LINQ 查询 C#应用程序中的 JSON

如果你正在为.NET 开发,你可能想完全跳过 JSONPath,并使用 Json.NET 的支持基于字段名称进行订阅,并支持 LINQ。Json.NET 内置支持 LINQ,让你可以针对你的 JSON 使用流畅或语句语法编写任何查询。

准备

与之前的食谱一样,你的.NET 项目需要使用 Json.NET。要将在项目中包含 Json.NET,请按照我在第七章以类型安全的方式使用 JSON中的入门部分所示的步骤操作,该章节为如何使用 Json.NET 反序列化对象食谱。

如何做到…

你将解析 JSON 以JObject,然后你可以针对结果JObject评估 LINQ 表达式,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;

static void Main(string[] args)
{
  var obj = JObject.Parse(json);
  var titles = from book in obj["store"]["book"] 
      select (string)book["title"];

  Console.WriteLine(titles.First());
}

当然,因为是 LINQ,也支持流畅语法:

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;

static void Main(string[] args)
{
  var sum = obj["store"]["book"]
             .Select(x => x["price"])
             .Values<double>().Sum();

  Console.WriteLine(sum);
}

它是如何工作的…

第一个示例选择所有的title对象,每个book字段中的一个,返回结果之前将每个对象转换为字符串。第二个示例对所有book的price字段进行选择,将结果值转换为双精度浮点数,并在列表上调用Sum方法,以获得所有书籍的总价格。

要留心的是,Json.NET LINQ 查询中的子字段的通常返回类型是JObject,所以你在使用流畅语法编写表达式时,必须使用JObject模板的Value和Values方法来获取这些对象的价值。你第一次尝试计算总和可能如下所示:

var s = obj["store"]["book"].
  Select(x =>x["price"]).Sum();

然而,这不会起作用,因为选择返回的值是一个JObject对象的列表,不能直接相加。

提示

当编写 LINQ 表达式时,LINQPad(www.linqpad.net)非常有帮助。如果你做大量的 LINQ 和 JSON,投资开发人员或高级版本可能是明智的,因为这些版本支持与 NuGet 的集成,让你可以将 Json.NET 直接包含在你的测试查询中。

请也参阅

关于 LINQ 和 Json.NET 的更多信息,请参阅位于www.newtonsoft.com/json/help/html/LINQtoJSON.htm的 Json.NET 文档。

第十章:JSON 在移动平台上的应用

当今的移动应用程序非常流行——像平板电脑和智能手机这样的设备在世界许多地方都超过了 PC 的销量。这些设备由 iOS 和 Android 等平台提供支持,并包括用于创建和解析 JSON 的 API,作为平台的一部分,使得作为应用程序开发者的你的生活变得稍微容易一些。在本章中,有你需要的食谱:

  • 在 Android 上解析 JSON

  • 在 Android 上生成 JSON

  • 使用 Objective-C 在 iOS 上解析 JSON

  • 使用 Objective-C 在 iOS 上生成 JSON

  • 使用 Swift 在 iOS 上解析 JSON

  • 使用 Swift 在 iOS 上生成 JSON

  • 使用 Qt 解析 JSON

  • 使用 Qt 生成 JSON

简介

正如我们在前一章中讨论的那样,JSON 是一种出色的媒介,用于与 Web 服务和服务器通信,无论客户端是 Web 应用程序还是传统应用程序。这对于移动应用程序尤其正确,许多移动应用程序运行在低带宽广域网上,在这种情况下,JSON 与 XML 相比的简洁性使得整体数据负载更小,从而确保远程查询的响应时间更快。

当今领先的移动平台是 Android 和 iOS。Android 运行 Linux 的一个变体,支持用 Java 进行软件开发,并在org.json命名空间中包括一个 JSON 处理器。iOS 从 Mach 和 BSD 中衍生而来,支持使用 Objective-C、Swift、C 和 C++进行软件开发,尽管对于大多数应用程序开发,你使用 Objective-C 或 Swift,每种语言都包含对NSJSONSerialization类的绑定,该类实现 JSON 解析和 JSON 序列化。

移动开发者的另一个选择是使用跨平台工具包,如 Qt,进行应用程序开发。Qt 在多种平台上运行,包括 Android、iOS 和 BlackBerry。Qt 定义了QJsonDocument和QJsonObject类,你可以使用它们来实现映射和 JSON 之间的相互转换。Qt 是一个存在已久的开源框架,不仅运行在移动平台上,还运行在 Mac OS X、Windows 和 Linux 上,以及其他许多平台。

我们接下来要讨论的 JSON 与前几章使用的类似,并像这样看起来像一个文档:

{
  'call': 'kf6gpe-7',
  'lat': 37.40150,
  'lng': -122.03683
  'result': 'ok'
}

在接下来的讨论中,我假设你已经正确地为目标平台设置了软件开发环境。描述为 Android、iOS 和 Qt 设置软件环境的流程会比这本书允许的空间要大。如果你对为特定的移动平台开发软件感兴趣,你可能想要查阅 Android 或 iOS 的开发者资源:

在 Android 上解析 JSON

Android 提供了 JSONObject 类,该类让你通过一个与地图概念上相似的接口来表示 JSON 文档的键值对,并包括通过访问器方法进行序列化和反序列化,这些方法可以访问 JSON 对象的命名字段。

如何做到…

你首先通过用你想要解析的 JSON 初始化 JSONObject,然后使用其各种 get 方法来获取 JSON 字段的值:

Import org.json.JSONObject;

String json = "…";
JSONObject data = new JSONObject(data);

String call = data.getString("call");
double lat = data.getDouble("lat");
double lng = data.getDouble("lng");

它是如何工作的…

JSONObject 构造函数接收要解析的 JSON,并提供访问器方法来访问 JSON 字段。在这里,我们使用 getString 和 getDouble 访问器分别访问 JSON 的 call、lat 和 lng 字段。

JSONObject 类定义了以下访问器:

  • get 方法,返回一个子类 java.lang.Object,其中包含命名槽中的值。

  • getBoolean 方法,如果槽中包含 Boolean 类型数据,则返回一个 Boolean 类型数据。

  • getDouble 方法,如果槽中包含 double 类型数据,则返回一个 double 类型数据。

  • getInt 方法,如果槽中包含 int 类型数据,则返回一个 int 类型数据。

  • getJSONArray 方法,如果槽中包含数组,则返回一个 JSONArray 实例,这是一个处理数组的 JSON 解析类。

  • getJSONObject 方法,如果槽中包含另一个映射,则返回一个 JSONObject 实例。

  • getLong 方法,如果槽中包含 long 类型数据,则返回一个 long 类型数据。

  • getString 方法,如果槽中包含 String 类型数据,则返回一个 String 类型数据。

该类还定义了 has 和 isNull 方法。这些方法接收一个槽的名字,如果字段中有值,或者没有该字段名或者值为 null,则返回 true。

JSONArray 与 JSONObject 类似,只不过它处理数组而不是映射。它有相同的访问器方法,这些方法采用集合中的整数索引,返回对象、布尔值、字符串、数字等等。

还有更多内容…

JSONObject 类还定义了 keys 方法,该方法返回 JSON 中的键的 Iterator<String>。你还可以通过调用 names 获得 JSON 中的名称的 JSONArray,或者通过调用 length 获得 JSON 中的键值对的数量。

也见

关于 JSONObject 的更多信息,请参阅 Android 文档中的 developer.android.com/reference/org/json/JSONObject.html。关于 JSONArray 的更多信息,请参阅 developer.android.com/reference/org/json/JSONArray.html。

在 Android 上生成 JSON

JSONObject 类还支持设置器方法来初始化 JSON 映射中的数据。利用这些方法,你可以将数据分配给一个 JSON 对象,然后通过调用其 toString 方法来获取 JSON 表示形式。

如何做到…

下面是一个简单示例:

import org.JSON.JSONObject;

JSONObject data = new JSONObject();
data.put("call", "kf6gpe-7");
data.put("lat", 37.40150);
data.put("lng", -122.03683);
String json = data.toString();

它是如何工作的…

多态的 put 方法可以接受整数、长整数、对象、布尔值或双精度浮点数,将你命名的槽分配给你指定的值。

JSONObject类定义了toString方法,它接受一个可选的空格数来缩进嵌套结构以进行美观的 JSON 打印。如果你不传递这个缩进,或者传递 0,实现尽可能地将 JSON 编码成最紧凑的形式。

还有更多…

还有一个putOpt方法,它接受任何Object的子类,如果名称和非空值都不为空,则将值放入名称中。

你可以通过传递JSONArray或将另一个JSONObject作为要设置的值来将槽分配给值数组。JSONArray定义了一个类似的 put 方法,它以数组中的整数索引作为第一个参数,而不是槽名。例如,使用前面示例中的数据对象,我可以使用以下代码添加一个在站点的测量电压数组(也许来自无线电的电池):

import org.JSON.JSONObject;

JSONArray voltages = new JSONArray();
voltages.put(3.1);
voltages.put(3.2);
voltages.put(2.8);
voltages.put(2.6);
data.put("voltages", voltages);

你也可以直接传递java.util.Collection和java.util.Map实例,而不是传递JSONArray或JSONObject实例。之前的代码也可以写成:

import org.JSON.JSONObject;
import org.JSON.JSONArray;
import java.util.Collection;

Collection<double> voltages = new Collection<double>();
voltages.put(3.1);
voltages.put(3.2);
voltages.put(2.8);
voltages.put(2.6);
data.put("voltages", voltages);

这使得在构建更复杂的 JSON 对象时稍微容易一些,因为你不必将每个 Java 集合或映射包装在一个相应的 JSON 对象中。

另见

关于JSONObject的更多信息,请参阅 Android 文档中的developer.android.com/reference/org/json/JSONObject.html。关于JSONArray的更多信息,请参阅developer.android.com/reference/org/json/JSONArray.html。

在 iOS 上用 Objective-C 解析 JSON

对象 C 的类库定义了NSJSONSerialization类,它可以进行 JSON 的序列化和反序列化。它将 JSON 转换为NSDictionary对象的值,这些对象的键是 JSON 中的槽名,值是它们的 JSON 值。它在 iOS 5.0 及以后的版本中可用。

如何做到…

这是一个简单的例子:

NSError* error;
NSDictionary* data = [ NSJSONSerialization
  JSONObjectWithData: json
  options: kNilOptions
  error: &error ];

NSString* call = [ data ObjectForKey: @"call" ];

它是如何工作的…

NSJSONSerialization类有一个方法JSONObjectWithData:options:error,它接受一个NSString、解析选项和一个记录错误的地方,并执行 JSON 解析。它可以接受顶层为数组或字典的 JSON,分别返回NSArray或NSDictionary结果。所有值必须是NSString、NSNumber、NSArray、NSDictionary或NSNull的实例。如果顶层对象是数组,该方法返回NSArray;否则,它返回NSDictionary。

还有更多…

默认情况下,此方法返回的数据是不可变的。如果你需要可变的数据结构,你可以传递选项NSJSONReadingMutableContainers。要解析不是数组或字典的顶层字段,请传递选项NSJSONReadingAllowFragments。

另见

关于类的 Apple 文档位于 developer.apple.com/library/ios/documentation/Foundation/Reference/NSJSONSerialization_Class/index.html。

使用 Objective-C 在 iOS 上生成 JSON

您还可以使用 NSJSONSerializer 类序列化 NSDictionary 或 NSArray;只需使用 dataWithJSONObject 方法。

如何进行…

这是一个简单的例子,假设数据是您想要转换为 JSON 的NSDictionary:

NSError *error;
NSData* jsonData = [NSJSONSerialization
dataWithJSONObject: data
options: NSJSONWritingPrettyPrinted
error: &error];

它是如何工作的…

dataWithJSONObject:options:error 方法可以接受 NSArray 或 NSDictionary 并返回一个包含您传递的集合编码 JSON 的 NSData 数据块。如果您传递 kNilOptions,JSON 将以紧凑的方式编码;要获取格式化的 JSON,请改为传递 NSJSONWritingPrettyPrinted 选项。

参见

关于 NSJSONSerialization 类的 Apple 文档位于 developer.apple.com/library/ios/documentation/Foundation/Reference/NSJSONSerialization_Class/index.html。

使用 Swift 在 iOS 上解析 JSON

相同的 NSJSONSerialization 类在 Swift 中可用,这是 Apple 为 iOS 开发推出的新语言。

如何进行…

这是一个如何调用 NSJSONSerialization 类的 JSONObjectWithData 方法的 Swift 示例:

import Foundation
var error: NSError?
Let json: NSData = /* the JSON to parse */
let data = NSJSONSerialization.JSONObjectWithData(json, 
  options: nil, 
  error: &error);

它是如何工作的…

Swift 中的方法调用看起来像函数调用,参数以可选命名的逗号分隔的形式传递,这与 C++或 Java 中的调用类似。JSONObjectWithData 方法的参数与 Objective-C 版本中的方法参数相同。

使用 Swift 在 iOS 上生成 JSON

当然,您也可以从 Swift 调用 NSJSONSerialization.dataWithJSONObject 方法,该方法返回一个 NSData 对象,您可以将其转换为字符串。

如何进行…

这是一个简单的例子:

var error: NSError?
var data: NSJSONSerialization.dataWithJSONObject(
  dictionary, 
  options: NSJSONWritingOptions(0),
  error: &error);
var json: NSString(data: data, encoding: NSUTF8StringEncoding); 

它是如何工作的…

dataWithJSONObject 方法的工作方式与其 Objective-C 对应方法相同。一旦我们收到包含字典 JSON 编码版本的 NSData,我们使用 NSString 构造函数将其转换为 NSString。

使用 Qt 解析 JSON

Qt 实现的 JSON 解析在其接口上实际上与 Android 版本非常相似。Qt 定义了 QJsonObject 和 QJsonArray 类,分别可以包含 JSON 映射和 JSON 数组。解析本身是由 QJsonDocument 类完成的,该类有一个静态的 fromJson 方法,接受 JSON 并执行必要的解析。

如何进行…

这是一个简单的例子:

QString json = "{ 'call': 'kf6gpe-7', 'lat': 37.40150, 'lng': -122.03683, 'result': 'ok'}";
QJsonDocument document = QJsonDocument.fromJson(json);
QJsonObject data = document.object;
QString call = data["call"].toString();

它是如何工作的…

解析是两步操作:首先,代码使用 QJsonDocument 解析 JSON,然后使用结果的 QJsonObject 访问数据。

QJsonObject 类作为 QJsonValue 对象的映射,每个对象都可以使用以下方法之一转换为其基本类型:

  • toArray: 这个方法转换为 QJsonArray。

  • toBool:这个方法转换为布尔值

  • toDouble:这个方法转换为双精度浮点数

  • toInt:这个方法转换为整数

  • toObject:这个方法转换为另一个QJsonObject,让你嵌套QJsonObject的映射

  • toString:这个方法转换为QString

还有更多内容…

你还可以使用 Qt 的foreach宏或者begin、constBegin和end迭代方法遍历QJsonObject中的键。此外,还有一个contain方法,它接受一个槽的名字,如果映射中包含你寻找的槽,则返回 true。

参见

参见 Qt 文档中关于 JSON 解析的部分:doc.qt.io/qt-5/json.html。

使用 Qt 生成 JSON

QJsonDocument类还有一个toJson方法,该方法将引用的对象转换为 JSON。

如何做到…

以下是一个示例,它将 JSON 转换为另一种 JSON,并在转换过程中格式化 JSON:

QString json = "{ 'call': 'kf6gpe-7', 'lat': 37.40150, 'lng': -122.03683, 'result': 'ok'}";
QJsonDocument document = QJsonDocument.fromJson(json);
QJsonObject data = document.object;
QByteArrayprettyPrintedJson = 
document.toJson(QJsonDocumented::Indented);    

它是如何工作的…

QJsonDocument类有一个toJson方法,该方法将引用的文档或数组转换为 JSON。你可以通过传递QJsonDocument::Indented来请求格式化后的 JSON 版本,或者通过传递QJsonDocument::Compact来请求紧凑的 JSON 版本。

参见

关于QJsonDocument的更多信息,请参阅 Qt 文档:doc.qt.io/qt-5/qjsondocument.html。

posted @ 2025-09-29 10:35  绝不原创的飞龙  阅读(61)  评论(0)    收藏  举报