二、在服务器上读写 JSON

在前一章中,我们研究了一些最常见的客户端环境中的 JSON 处理。 在本章中,我们将把注意力转向服务器端 JSON 编码和解码。 我们将看看如何在以下环境中做到这一点:

  • 在 Clojure 中读写 JSON
  • 在 f#中读写 JSON
  • 在 Node.js 中读写 JSON
  • 在 PHP 中读写 JSON
  • 在 Ruby 中读写 JSON

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

在 Clojure 中读写 JSON

Clojure 是现代 Lisp 版本运行在 Java 和微软共同*语言运行时【显示】(CLR)平台。 因此,您可以使用设施第 1 章中我们讨论了【病人】,阅读和写作 JSON 在客户机上,把 JSON 和对象之间的【t16.1】本机运行时,但是去那里有一个更好的方式,这就是 Clojure 的data.json模块,可以在 https://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的版本可能会在我写这篇文章的时间和你把它作为依赖项纳入你的项目的时间之间发生变化。 核对一下数据。 Json 项目的当前版本。

最后,您需要在您的代码中包含data.json模块,例如json:

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

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

How to do it…

将 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 的映射。

There's more…

这些方法都接受两个可选参数,模块应用于每个 JSON 属性名的:key-fn参数,以及模块应用于属性值的: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"}"

value-fn在解析 JSON 时将 ISO 日期作为字符串转换为 JavaDate对象:

(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. 定义一个 helper 函数my-value-reader,它使用 JSON 键值对的关键字来确定其类型。
  2. 给定一个 JSON 键值:date,它将该值作为一个字符串传递给java.sql.Date方法valueOfvalueOf方法返回一个Date实例,该实例的值来自它所解析的字符串。
  3. 调用json/read-str来解析由resultdate两个字段组成的简单 JSON。
  4. JSON 解析器解析 JSON,将 JSON 属性名称转换为关键字,使用我们之前定义的值转换器将日期值转换为其java.sql.Date表示。

f#中读写 JSON

f#是一种运行在 CLR 和。net 上的语言,擅长于函数式和面向对象编程任务。 因为它是在。net 之上,你可以使用第三方库,比如Json.NET(第一章中提到的,阅读和写作 JSON 在客户机上)之间的转换【显示】JSON 和 CLR 对象。 然而,有一个更好的方法:开源库 f# Data,它创建了原生数据类型提供程序来处理各种不同的结构化格式的数据,包括 JSON。

准备好

从开始获取库的副本,可在https://github.com/fsharp/FSharp.Data上获得。 一旦你下载了它,你便需要去创造它; 你可以通过运行发行版附带的build.cmd构建批处理文件来做到这一点(详情请参阅 f# Data 网站)。 或者,您可以在 NuGet 上找到相同的包,选择管理 NuGet 包项目菜单和搜索 f#数据。 找到后,点击安装。 我更喜欢使用 NuGet,因为它会自动将FSharp.Data程序集添加到您的项目中,并为您节省了自己构建源代码的麻烦。 另一方面,源代码发行版使文档可以离线阅读,这也很方便。

一旦你有了 f# Data,你只需要在源文件中打开它,然后用 open 指令使用它,像这样:

open FSharp.Data

How to do it…

下面是一些示例代码,它在一些 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"

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

How it works…

首先,重要的是要记住 f#是强类型的,并从数据推断出类型。 理解这一点对于理解 f# Data 库是如何工作的至关重要。 与我们在过去几节中看到的示例不同,转换器将 JSON 映射到键值对,f# Data 库从您提供给它的 JSON 推断出整个数据类型。 在许多方面,这是最好的的动态 collection-oriented 方法,其他转换器转换为 JSON,类型安全的方法,我将向您展示在第七章,使用 JSON 类型安全的方式。 这是因为您不需要费力地为正在解析的 JSON 编写类表示,并且您在编写的代码中获得了编译时类型安全的所有优势。 更好的是,f# Data 构造的类都是智能感知的,所以您可以在编辑器中获得工具提示和名称完成!

让我们一段一段地看一下前面的例子,看看它是怎么做的:

open FSharp.Data

第一行使 f# Data 类对您的程序可用。 除此之外,这定义了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 传递给JsonProviderParse方法? 回想一下,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。

There's more…

f# Data 库支持的不仅仅是 JSON; 它还支持逗号分隔值(CSV),HTML, XML。 它是一个用于进行各种结构化数据访问的优秀库,如果您使用 f#,它绝对是您需要更加熟悉的东西。

使用 Node.js 读写 JSON

Node.js 是一个用于服务器端编程的 JavaScript 环境,基于相同的高性能 JavaScript 运行时谷歌为 Chrome 构建,由 Joyent 支持。 它的高性能和异步编程模型使它成为定制 web 服务器的极佳环境,主要公司,包括沃尔玛,在生产设置中使用它。

准备好

因为我们将在接下来的两章中使用 Node.js,所以有必要向您指出如何下载和安装它,即使您的日常服务器环境更像是 Apache 或 Microsoft IIS。 您将需要去http://www.nodejs.org/并从首页下载安装程序。 这将安装所有你需要运行 Node.js 和 npm, Node.js 使用的包管理器。

提示

在 Windows 上安装之后,我必须重新启动 Windows shell 以正确地找到 node .js 安装程序所安装的节点和 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命令服务的端口的访问。

How to do it…

由于 Node.js 使用 Chrome 的 V8 JavaScript 引擎,所以在 Chrome 中使用 JSON 和 Node.js 是一样的。 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 对象,我们在这里将该数据赋给变量对象。

当然,你可以用做相反的事,用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第 1 章在客户端上读写 JSON

用 PHP 读写 JSON

PHP 是一个流行的服务器端脚本环境,很容易与 Apache 和 Microsoft IIS web 服务器集成。 它支持简单的 JSON 编码和解码。

How to do it…

PHP 提供了两个函数,json_encodejson_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函数有三个可选参数:

  • 第一个参数为 true 时,指定结果应该以关联数组返回,而不是以stdClass类型的对象返回。
  • 第二个参数指定可选的递归深度,以确定解析器应该解析 JSON 的深度。
  • 第三个参数可能是选项JSON_BIGINT_AS_STRING,当 set 表示溢出整数值的整数应该作为字符串返回,而不是转换为浮点数(这可能会失去精度)。

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

用 Ruby 读写 JSON

Ruby 提供了jsongem 用于 JSON 处理。 在较早的版本的 Ruby 中,你必须自己安装这个 gem; 它是从 Ruby 1.9.2 开始的基础安装的部分。

准备好

如果你运行的是比 Ruby 1.9.2 更早版本的 Ruby,首先使用以下命令安装 gem:

gem install json

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

gem install json_pure

不管您是否需要安装 gem,您都需要在代码中包含它。 要做到这一点,包括rubygemsjsonjson/纯,取决于你安装的宝石; 使用require,像这样:

require 'rubygems'
require 'json'

前面的代码处理前者,而下面的代码处理后者:

require 'rubygems'
require 'json/pure'

How to do it…

gem 定义了 JSON 对象,其中包括分别序列化 JSON 和反序列化 JSON 的parsegenerate方法。 使用它们是你现在所期望的。 创建一个对象或一些 JSON,调用适当的函数,并查看结果。 例如:使用 JSON 创建一些 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,如果设置为 true,则允许解析 RFC 4627 中的 NaN、Infinity 和-Infinity。
  • symbolize_names为 true 时,返回 JSON 对象中属性名的符号; 否则,将返回字符串(默认为字符串)。

参见

JSON Ruby gem 文档可在 Web 上获得http://flori.github.io/json/doc/index.html