JavaScript Object Notation(JSON)是一种纯文本的数据交换格式,它基于第三版 ECMA 262 标准的子集。JSON 被用作将数据结构序列化为字符串的机制。这些字符串通常通过网络发送、写入输出文件或用于调试。JSON 经常被吹捧为“XML 的无脂肪替代品”,因为它提供了与 XML 相同的功能,但通常需要更少的字符。与 XML 相比,JSON 也更容易解析。由于 JSON 的简单性和低开销,许多开发人员放弃了 XML,转而使用 JSON。
从语法上来说,JSON 非常类似于 JavaScript 的对象字面语法。JSON 对象以左花括号{开始,以右花括号}结束。花括号之间是零个或多个键/值对,称为成员。成员由逗号分隔,而冒号用于将成员的键与其对应的值分隔开。密钥必须是用双引号括起来的字符串。这是与 object literal 语法的最大区别,object literal 语法允许双引号、单引号或根本没有引号。值的格式取决于其数据类型。清单 A-1 显示了一个通用的 JSON 字符串。
清单 。JSON 对象的一般示例
{"key1": value1, "key2": value2, ..., "keyN": valueN}
注一段 JSON 的根几乎总是一个对象。然而,这不是绝对的要求。顶层也可以是数组。
支持的数据类型
JSON 支持许多 JavaScript 的原生数据类型。具体来说,JSON 支持数字、字符串、布尔、数组、对象和null。本节介绍了与每种受支持的数据类型相关的详细信息。
数字
JSON 数字不能有前导零,小数点后必须至少有一个数字(如果有一个的话)。由于前导零的限制,JSON 只支持十进制数字(八进制和十六进制都需要前导零)。如果您想包含其他基数的数字,必须先将它们转换为基数为 10 的数字。在清单 A-2 中,创建了四个不同的 JSON 字符串。所有 JSON 字符串都定义了一个名为foo的字段,保存十进制值100。在第一个字符串中,foo的值来自整数常量100。在第二个字符串中,foo的值来自以 10 为基数的变量decimal。第三个字符串json3的值来自基数为 8 的变量octal,而json4的值来自基数为 16 的变量hex。所有的字符串都产生相同的 JSON 字符串,尽管有些变量有不同的基数。这是可能的,因为变量octal和hex在字符串连接过程中被隐式转换为基数为 10 的数字。
清单 A-2 。JSON 字符串中使用的数字示例
var decimal = 100;
var octal = 0144; // JavaScript octals have a leading zero
var hex = 0x64; // JavaScript hex numbers begin with 0x
var json1 = "{\"foo\":100}";
var json2 = "{\"foo\":" + decimal + "}";
var json3 = "{\"foo\":" + octal + "}";
var json4 = "{\"foo\":" + hex + "}";
// all JSON strings are {"foo":100}清单 A-3 中的所示的字符串不是有效的 JSON,因为非十进制数字被直接构建到字符串中。在这个例子中,八进制和十六进制文字没有机会被转换成它们的十进制等价物。
清单 A-3 。JSON 字符串中无效数值的示例
var json1 = "{\"foo\":0144}";
var json2 = "{\"foo\":0x64}";字符串
JSON 字符串非常类似于普通的 JavaScript 字符串。但是,JSON 要求字符串用双引号括起来。尝试使用单引号会导致错误。在 A-4 的清单中,用一个名为foo的字段创建了一个 JSON 字符串,该字段的字符串值为bar。
清单 。包含字符串数据的 JSON 字符串示例
var json = "{\"foo\":\"bar\"}";
// json is {"foo":"bar"}布尔型
JSON 布尔值与普通的 JavaScript 布尔值相同,只能保存值true和false。清单 A-5 中的示例创建了一个带有两个字段foo和bar的 JSON 字符串,它们分别保存布尔值true和false。
清单 A-5 。包含布尔数据的 JSON 字符串示例
var json = "{\"foo\":true, \"bar\":false}";
// json is {"foo":true, "bar":false}数组
一个数组是一个有序的值序列。JSON 数组以左方括号[开始,以右方括号]结束。括号之间是零个或多个值,用逗号分隔。所有的值不必都是相同的数据类型。数组可以包含 JSON 支持的任何数据类型,包括嵌套数组。清单 A-6 中的显示了几个包含数组的 JSON 字符串。在json1中定义的foo数组为空,而在json2中定义的数组包含两个字符串。在json3中定义的foo数组更加复杂——它包含一个数字、一个布尔值、一个字符串嵌套数组和一个空对象。
清单 A-6 。JSON 字符串中的数组示例
var json1 = "{\"foo\":[]}";
var json2 = "{\"foo\":[\"bar\", \"baz\"]}";
var json3 = "{\"foo\":[100, true, [\"bar\", \"baz\"], {}]}";
// json1 is {"foo":[]}
// json2 is {"foo":["bar", "baz"]}
// json3 is {"foo":[100, true, ["bar", "baz"], {}]}对象
一个对象是一个无序的键/值对集合。与数组一样,对象可以由 JSON 支持的任何数据类型组成。列出 A-7 的中的例子展示了 JSON 对象是如何相互嵌套的。
清单 。JSON 中嵌套对象的一个例子
var json = "{\"foo\":{\"bar\":{\"baz\":true}}}";
// json is {"foo":{"bar":{"baz":true}}}null
JSON 中也支持 JavaScript 的null数据类型。清单 A-8 创建一个 JSON 字符串,带有一个名为foo的null值字段。
清单 A-8 。在 JSON 字符串中使用null数据类型
var json = "{\"foo\":null}";
// json is {"foo":null}不支持的数据类型
JSON 不支持许多 JavaScript 的内置数据类型。这些类型是undefined,内置对象Function、Date、RegExp、Error和Math. undefined的值根本无法在 JSON 中表示,但是其他不受支持的类型可以表示,如果您稍微有点创造力的话。为了序列化不支持的数据类型,必须首先将其转换成 JSON 兼容的其他表示形式。尽管没有标准化的方法,但是这些数据类型中的许多都可以使用toString()方法简单地转换成字符串。
使用 JSON 的函数
考虑到必须考虑所有的大括号和中括号,处理原始 JSON 字符串可能是乏味且容易出错的。为了避免这种繁琐,JavaScript 提供了一个全局的JSON对象来处理 JSON 数据。JSON对象包含两个方法——stringify()和parse()——用于将对象序列化为 JSON 字符串,并将 JSON 字符串反序列化为对象。本节详细解释了这些方法的工作原理。
JSON.stringify()
JSON.stringify()是将 JavaScript 对象序列化为 JSON 字符串的推荐方法。清单 A-9 中的显示了stringify()的语法。第一个参数value是被字符串化的 JavaScript 对象。另外两个参数replacer和space是可选的,可以用来定制字符串化过程。这些争论将很快被重新讨论。
清单 A-9 。JSON.stringify()方法的使用
JSON.stringify(value[, replacer[, space]])toJSON()法
有几种方法可以定制字符串化过程。这方面的一个例子是使用toJSON()方法。在序列化过程中,JSON 检查对象是否有名为toJSON()的方法。如果这个方法存在,那么它被stringify()调用。stringify()将序列化toJSON()返回的任何值,而不是处理原始对象。JavaScript 的Date对象就是这样被序列化的。由于 JSON 不支持Date类型,Date对象配备了toJSON()方法。
列出 A-10 显示toJSON()在行动。在这个例子中,一个名为obj的对象是用字段foo、bar和baz创建的。当obj被字符串化时,它的toJSON()方法被调用。在这个例子中,toJSON()返回一个obj的副本,减去foo字段。obj的副本被序列化,产生一个只包含bar和baz字段的 JSON 字符串。
清单 。使用自定义toJSON()方法的示例
var obj = {foo: 0, bar: 1, baz: 2};
obj.toJSON = function() {
var copy = {};
for (var key in this) {
if (key === "foo") {
continue;
} else {
copy[key] = this[key];
}
}
return copy;
};
var json = JSON.stringify(obj);
console.log(json);
//json is {"bar":1,"baz":2}replacer论据
JSON.stringify()的replacer参数可以用作一个函数,它接受两个表示键/值对的参数。首先,使用空键调用函数,对象被序列化为值。为了处理这种情况,replacer()函数必须检查空字符串是否是键。接下来,每个对象的属性和相应的值被一个接一个地传递给replacer()。由replacer()返回的值用于字符串化过程。清单 A-11 中显示了一个没有定制行为的示例replacer()函数。
清单 A-11 。没有自定义行为的示例replacer()函数
function(key, value) {
// check for the top level object
if (key === "") {
return value;
} else {
return value;
}
}正确处理顶级对象很重要。通常,最好简单地返回对象的值。在清单 A-12 的示例中,顶级对象返回字符串foo。因此,无论如何处理对象的属性,stringify()总是返回foo。
清单 A-12 。将任何对象序列化为字符串foo的replacer()函数
function(key, value) {
if (key === "") {
return "foo";
} else {
// this is now irrelevant
return value;
}
}在清单 A-13 的中,使用名为filter()的自定义replacer()函数序列化一个对象。filter()函数的工作是只序列化数值。所有非数字字段都将返回一个undefined值。返回undefined的字段会自动从字符串对象中移除。在这个例子中,replacer()函数导致baz被删除,因为它保存了一个字符串。
清单 A-13 。一个仅序列化数字的示例函数replacer()
function filter(key, value) {
// check for the top level object
if (key === "") {
return value;
} else if (typeof value === "number") {
return value;
}
}
var obj = {foo: 0, bar: 1, baz: "x"};
var json = JSON.stringify(obj, filter);
console.log(json);
// json is {"foo":0,"bar":1}replacer的数组形式
replacer参数也可以保存一个字符串数组。每个字符串表示应该序列化的字段的名称。任何不包含在replacer数组中的字段都不会包含在 JSON 字符串中。在清单 A-14 的示例中,一个对象被定义为带有名为foo和bar的字段。还定义了一个数组,包含字符串foo和baz。在字符串化过程中,bar字段被删除,因为它不是replacer数组 的一部分。请注意,没有创建baz字段,因为尽管它在replacer数组中定义,但它没有在原始对象中定义。这使得foo成为 stringified 对象中唯一的字段。
清单 A-14 。将replacer参数作为数组的示例
var obj = {foo: 0, bar: 1};
var arr = ["foo", "baz"];
var json = JSON.stringify(obj, arr);
console.log(json);
// json is {"foo":0}space论据
JSON 字符串通常用于日志记录和调试目的。为了提高可读性,stringify()函数支持名为space的第三个参数,它允许开发人员格式化生成的 JSON 字符串中的空白。该参数可以是数字或字符串。如果space是一个数字,那么最多 10 个空格字符可以用作空格。如果该值小于 1,则不使用空格。如果该值超过 10,则使用最大值 10。如果space是一个字符串,那么这个字符串被用作空白。如果字符串长度大于 10,则只使用前 10 个字符。如果省略space或null,则不使用空白。清单 A-15 展示了如何使用space参数。
清单 A-15 。使用space参数的字符串化示例
var obj = {
foo: 0,
bar: [null, true, false],
baz: {
bizz: "boff"
}
};
var json1 = JSON.stringify(obj, null, " ");
var json2 = JSON.stringify(obj, null, 2);
console.log(json1);
console.log(json2);在清单 A-15 中,json1和json2中的 JSON 字符串最终是相同的。产生的 JSON 如清单 A-16 所示。请注意,该字符串现在跨越了多行,并且随着嵌套的增加,属性多缩进了两个空格。对于重要的对象,这种格式极大地提高了可读性。
清单 A-16 。在清单 A-15 的中生成的格式化的 JSON 字符串
{
"foo": 0,
"bar": [
null,
true,
false
],
"baz": {
"bizz": "boff"
}
}JSON.parse()
要从 JSON 格式的字符串构建 JavaScript 对象,可以使用JSON.parse()方法。parse()提供与stringify()相反的功能。它被用作比eval()更安全的选择,因为eval()将执行任意的 JavaScript 代码,而parse()被设计为只处理有效的 JSON 字符串。
清单 A-17 。JSON.parse()方法的使用
JSON.parse(text[, reviver])在清单 A-18 中,parse()方法用于从 JSON 字符串构建一个对象。存储在obj中的结果对象有两个属性——foo和bar——分别保存数值 10 和 20。
清单 A-18 。使用JSON.parse()反序列化 JSON 字符串的例子
var string = "{\"foo\":10, \"bar\":20}";
var obj = JSON.parse(string);
console.log(obj.foo);
console.log(obj.bar);
// obj.foo is equal to 10
// obj.bar is equal to 20reviver()论点
parse()、reviver()的第二个参数是一个函数,允许在解析过程中转换对象。每个属性都是从 JSON 字符串中解析出来的,它通过reviver()函数运行。由reviver()返回的值在构造的对象中被用作属性值。如果reviver()返回一个undefined值,那么该属性将从对象中移除。
reviver()函数有两个参数,属性名(key)和它的解析值(value)。reviver()应该总是检查空字符串的key参数。原因是,在每个单独的属性上调用reviver()之后,在构造的对象上调用。在最后一次调用reviver()时,空字符串作为key参数传递,构造的对象作为value传递。考虑到这种情况,清单 A-19 中的显示了一个没有定制的示例reviver()功能。
清单 A-19 。reviver()功能示例
function(key, value) {
// check for the top level object
if (key === "") {
// be sure to return the top level object
// otherwise the constructed object will be undefined
return value;
} else {
// return the original untransformed value
return value;
}
}清单 。使用JSON.parse()和自定义reviver()函数的示例
function square(key, value) {
if (key === "") {
return value;
} else {
return value * value;
}
}
var string = "{\"foo\":10, \"bar\":20}";
var obj = JSON.parse(string, square);
console.log(obj.foo);
console.log(obj.bar);
// obj.foo is 100
// obj.bar is 400
注意JSON.parse()和JSON.stringify()都是可以抛出异常的同步方法。因此,这些方法的任何使用都应该包装在一个try...catch语句中。
摘要
JSON 在 Node 生态系统中得到了广泛的应用,这一点您现在肯定已经看到了。例如,任何值得使用的包都会包含一个package.json文件。事实上,为了使模块与npm一起使用,需要一个package.json。几乎每个数据 API 都是使用 JSON 构建的,因为 Node 社区更倾向于 JSON,而不是 XML。因此,理解 JSON 对于有效使用 Node 至关重要。幸运的是,JSON 很容易阅读、编写和理解。阅读完本章后,您应该对 JSON 有足够的了解,可以在您自己的应用中使用它,或者与其他应用进行交互(例如,RESTful web 服务)。