JSON 全名:JavaScript Object Notation,是一种轻量级的基于文本的数据交换格式,它源自 JavaScript 这种脚本语言,用于表示简单的数据结构。如果我们抛开 JavaScript 语言单纯地看待 JSON,可以说它是语言无关的一种数据结构,正是由于它的这种特性,它经常被用来作为 XML 的一种“替代品”。
Dojox 中的 JSON 工具包主要包括 Dojox.JSON 和 Dojox.JSONPath,其中 JSON 包包含查询定位、schema 定义验证以及引用(ref)等,JSONPath 则主要侧重于模拟 XPath 查询。本文会依次介绍 JSON 和 JSONPath 包中的各种接口以及它的一些使用方式和技巧。
其实从字面意义上来看,大家想必都知道这就是用于检索 JSON 对象内容的接口,它可以快速定位和快速查询出 JSON 对象中我们需要的内容。我们还是通过几个实例来介绍它的用法吧。
先来看一个 JSON 对象的示例:
清单 1. 目标 JSON 对象
dojox.json.tests.testData= {
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\nRings",
"isbn":"0-395-19395-8",
"price":22.99
}
],
"bicycle": {
"color":"red",
"price":19.95
}
},
"symbols":{"@.$;":5}
};
|
可见,这是个相对比较复杂的 JSON 串。我们可以通过如下的一些方式快速检索到我们要的信息:
清单 2. 检索案例 1
var result = dojox.json.query("$.store.book[=author]" ,dojox.json.tests.testData);
-- '["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]';
|
这里最关键的就是"$.store.book[=author]"该如何翻译了:
“$”这里表示根节点,即“testData”数据本身。
“$.store.book”表示根节点的直接子节点“store”的直接子节点“book”,它的检索行为和 JavaScript 里面对 JSON 数据的“.”操作行为基本是一致的。
“[=author]”表示 book 节点里面键(key)为“author”的节点值,即只取键为“author”的节点值。所以,返回值应为所有的键为“author”的节点值数组,转化为字符串即为'["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]'。
清单 3. 检索案例 2
var result = dojox.json.query("$..author" ,dojox.json.tests.testData);
-- '["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]';
|
“..”操作符(两个点)表示递归查找所有的子节点(包括直接的和间接的子节点)。
所以,这里的返回值应为所有的键为“author”的节点值数组,转化为字符串即为'["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]'。结果同检索案例 1。
清单 4. 检索案例 3
var result = dojox.json.query("$.store.*" ,dojox.json.tests.testData);
-- '
[[{"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\\nRings","isbn":"0-395-19395-8","price":22.99}],
{"color":"red","price":19.95}]';
|
再来看一个特殊通配符“*”,它表示返回所有值,即这里表示“store”下面的所有属性和值,所以它会返回一系列内容。
接下来我们来看看属性值匹配:
清单 5. 检索案例 4
var result = dojox.json.query("$..book[0]?price=22.99" ,dojox.json.tests.testData);
-- '[{"category":"fiction","author":"J. R. R. Tolkien",
"title":"The Lord of the\\nRings","isbn":"0-395-19395-8",
"price":22.99}]';
|
这种方式属于“[?expression]”方式,它是针对数组对象的过滤,即过滤出数组对象里面满足 expression 的所有元素,这里就是得到数组里面 price 为 22.99 的那个或那几个元素。
清单 6. 检索案例 5
var result = dojox.json.query("$..book[0][0,1]" ,dojox.json.tests.testData);
-- '[{"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}]';
var result = dojox.json.query("$..book[0][:2]" ,dojox.json.tests.testData);
-- '[{"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}]';
|
比较上述两种查询,其实区别就在于“,”号和“:”号,“[0,1]”表示数组中第 0 个和第 1 个的联合数组,它也可以为属性值匹配表达式(expression),而“[:2]”是一个截断操作,即从数组开始(第一个元素)到下标为 2 的元素之前的所有元素,“[:2]”等同于“[0:2]”。可以看出,其实这两个查询的返回值是一样的。
清单 7. 检索案例 6
var result = dojox.json.query("$..[^?author~'herman melville']" ,
dojox.json.tests.testData);
-- '[{"category":"fiction","author":"Herman Melville",
"title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}]';
|
这里我们再来看看两个新的通配符,“^”:它表示取出重复,相当于 sql 里面的“distinct”,所以这里在找出最终元素后,会去除返回值中重复的元素。“~”是专门用于匹配字符串的,注意,它不区分大小写。
再来看一个稍微复杂一点的:
清单 8. 检索案例 7
var result = dojox.json.query("$..[^?@['author']='Herman*']" ,
dojox.json.tests.testData);
-- '[{"category":"fiction","author":"Herman Melville","title":"Moby Dick",
"isbn":"0-553-21311-3","price":8.99}]';
|
其实也很简单,“@['author']”就是代表 author 属性,“'Herman*'”用于模糊匹配,所以这里的返回值仍是 author 为“Herman Melville”的那个对象。其实“@”是相当于检索到的当前对象的一个引用,“@['author']”其实也相当于“@.author”。
其实,Dojo 的检索接口还支持传入参数,来看下面一个例子:
清单 9. 检索案例 8
var result = dojox.json.query("$..book[0]?author=$1&price=$2" ,
dojox.json.tests.testData, "Herman Melville", 8.99);
-- '[{"category":"fiction","author":"Herman Melville",
"title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}]';
|
这里可以看到“author=$1&price=$2”中“$1”,“$2”这样的标签,这就是形参,可以在随后的 Query 接口中传入实参:“"Herman Melville", 8.99”。
清单 10. 检索案例 9
var result = dojox.json.query("$.store.book[/category][/price][=price]" ,
dojox.json.tests.testData);
-- '[8.95,8.99,12.99,22.99]';
var result = dojox.json.query("$.store.book[\\category,\\price][=price]" ,
dojox.json.tests.testData);
-- '[8.95,22.99,12.99,8.99]';
|
“/”表示升序排序,“\”表示降序排序。第一个例子是“category”和“price”分别排序,所以会以后一个 price 的排序为主,所以结果为:“[8.95,8.99,12.99,22.99]”。而第二个排序为多排序,只是靠前的优先级比较高,即靠前的属性先排序,然后基于前一个排好序的序列,进行后面的后面的排序。所以结果为“[8.95,22.99,12.99,8.99]”。(这里“8.95”在最前的原因就是 category 先排了一次序,致使 reference 在 fiction 之前)
其实,Dojo 的 Query 接口还支持类似“计算”的功能。
清单 11. 检索案例 10
var result = dojox.json.query( "$.store.book[\\price][0].price - $.store.book[/price][0].price" , dojox.json.tests.testData); -- 14; |
可以看出,以上为降序排在第一位的 price 值“减去”升序排在第一位的 price,即 22.99 - 8.99 = 14。
这个 JSONPath 接口集的功能其实和之前介绍的 Query 接口相似,只是侧重点不同:Query 接口是建立在自身定义的检索过滤通配符规则之上,而这里的 JSONPath 的侧重点主要是模拟 XPath 来实现一套快速定位和检索的功能。
Query 接口和 JSONPath 接口在使用上其实有很大程度的相似性,它们的检索规则,通配符都比较相近,在 JSONPath 中解释如下:
$ 根元素
@ 当前节点
. or [] 子节点操作符
.. 后代节点
* 所有对象,即通用匹配
[] 属性匹配
[,] 联合处理器
[start:end:step] 数组切割
?() 条件过滤器
() 表达式(属性值匹配等)
其实,与 Query 接口大致相近。我们看几个例子:
清单 12. 目标 JSON 对象(JSONPath)
var 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 对象,我们例举出如下输入输出共大家参考:
清单 13. 综合检索
$.store.book[*].author
-- ["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]
$.store..price
-- [8.95,12.99,8.99,22.99,19.95]
$..book[(@.length-1)]
-- [{"category":"fiction","author":"J. R. R. Tolkien",
"title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}]
$..book[0,1]
-- [{"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}]
$..*
-- [{"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},
.............
|
这里我们只是简单的举了几个例子,其中“$..book[(@.length-1)]”表示返回 book 中最后一个元素。“$..*”表示返回根节点下面的所有内容。可见,其用法基本与 Query 相似。
JSONPath 的接口使用方式和 Query 接口也大致相当,见如下:
清单 14. 检索案例 10
var result = dojox.JSONPath.query(dojox.JSONPath.tests.testData, "$..book[0,1]")
-- '[{"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}]'
|
大家可以根据自己的实际需要,选择 Query 或者 JSONPath 包。
这个接口主要提供了一个可以通过“引用”来构造和解析 JSON 对象,这使得我们可以省去很多 JSON 对象里重复的内容,转而用引用替代;同时,还能够让我们构造更加复杂的 JSON 对象。我们先来看看如下示例代码:
清单 15. 简单的引用
var testStr = '{
a:{$ref:"#"},
id:"root",
c:{d:"e",f:{$ref:"root.c"}},
b:{$ref:"#.c"},
"an array":["a string"],
"a string":{$ref:"#an array.0"}
}';
|
这里大家可以看到一些特殊的标记,我们来一一解释:
{$ref:"#"}:这里代表它就是根对象的引用,也就是说“a:{$ref:"#"}”中“a”的值就是这个 JSON 对象本身(根对象)。
{$ref:"root.c"}:这里表示它是 id 为“root”的对象下面的“c”属性的引用。
{$ref:"#.c"}:其实“#”这里就代表根元素,即就是对象本身。
{$ref:"#an array.0"}:这里“#an array”代表根元素下面的键(key)为“an array”的对象引用。
我们可以通过如下方法将它从字符串转为 JSON 对象:
清单 16. 字符串转 JSON
var testStr = '{
a:{$ref:"#"},
id:"root",
c:{d:"e",f:{$ref:"root.c"}},
b:{$ref:"#.c"},
"an array":["a string"],
"a string":{$ref:"#an array.0"}
}';
var mirrorObj = dojox.json.ref.fromJson(testStr);
|
转为 JSON 对象(mirrorObj)后,可以了解到,由于该 JSON 字符串中有很多引用,所以可以得出里面的很多相等的关系,见如下代码:
清单 17. 有引用的 JSON 对象
mirrorObj==mirrorObj.a mirrorObj.c==mirrorObj.c.f mirrorObj.c==mirrorObj.b mirrorObj["a string"]=="a string" |
“mirrorObj.a”为“{$ref:"#"}”,所以它就是“mirrorObj”本身。
“mirrorObj.c.f”为“{$ref:"root.c"}”,所以它就是根元素的“c”对象,就是“mirrorObj.c”。
“mirrorObj.b”为“{$ref:"#.c"}”,显然它就是“mirrorObj.c”。
“mirrorObj["a string"]”为“{$ref:"#an array.0"}”,就是根元素的"an array"键值的第 0 个元素,即为"a string"。
再来看一个引用的例子:
清单 18. 关于数组
var testStr = '[{$ref:1,foo:"bar"},{$ref:2, me:{$ref:2},first:{$ref:1}}]';
|
可见这里是一个顶层为数组类型的 JSON 字符串,“$ref:1”指代数字的第 1 个元素(数组的第 0 个)。所以有如下结论:
清单 19. 数组元素的引用
var mirrorObj = dojox.json.ref.fromJson(testStr); mirrorObj[0].foo=="bar" mirrorObj[1]==mirrorObj[1].me mirrorObj[0]==mirrorObj[1].first |
其中“mirrorObj[1].first”为“{$ref:1}”,即为“mirrorObj[0]”。
其实它主要用于判断一个 JSON 对象是否满足某个定义规则。我们知道每个 XML 都会有一个 DTD 定义,这个 XML 必须满足该 DTD 定义的规则才能算得上是一个合法的 XML。同样,我们的 JSON 也有这种定义。
这种定义的作用非常广泛:如果我们加入了这样的一些预先定义的规则限制,那么,在我们解析 XML 或者 JSON 的时候,可以先验证一下它的合法性,这样我们就不用在读取 XML 或者 JSON 数据时做一些额外的验证或者判断了,因为经过合法性验证的 XML 或者 JSON 必然符合我们的要求(包括数据格式和类型)。在这里我们就基于 Dojox.JSON 包中的 schema 接口来介绍一下 JSON 中的“DTD”。
Dojox 中的“DTD”也是 JSON 格式,参见如下一种定义(schema):
清单 20. 简单的 schema
var biggerSchema = {
"readonly":true,
"type":"object",
"properties":{
"name": {"type":"string",
length:5,
"optional":false},
"tuple":{items:[{"type":"integer"},
{"type":"string"},
{"type":"boolean"}]},
"born" : {"type":["number","string"],
//allow for a numeric year, or a full date
"format":"date", //format when a string value is used
"minimum":1900, //min/max for when a number value is used
"maximum":2010
},
"gender" : {"type":"string",
"enum":["male","female"],
"options": [{"label":"Male",value:"male"},
{"label":"Female",value:"female"}]
},
"address" : {"type":"object",
"properties":
{"street":{"type":"string"},
"city":{"type":"string"},
"state":{"type":"string"}}
}
}
};
|
我们来看看这个 JSON 格式的 schema:
"readonly":true,代表待验证的 JSON 对象应为只读。
"type":"object",代表待验证的 JSON 对象应为键 - 值对的对象格式(区别于数组)。
"properties",里面包含了该 JSON 对象支持的所有属性的键,以及其对应的值应满足的条件。
"name"中,这里的"type":"string"代表其值应为字符串类型,"optional":false 表示为必须项(键),length:5 表示不能小于 5 个字符。
"tuple"中,这种写法表示它的值为数组类型,第一个为整型,第二个为字符串,第三个为布尔值。
"born"中,"type":["number","string"] 表示它的取值类型可以为数字或者字符串,"format":"date"表示如果值为字符串类型,则会格式化成 Date 类型,如果为数字,则其允许的最小值为 1900,最大值为 2010。
"gender"中,其值的类型须为字符串,"enum":["male","female"] 表示该字符串的值须在"male"和"female"中二者选其一。
"address"中,其实它与"tuple"有点类似,只是这里为对象而不是数组,并且该对象有三个键值"street","city","state",它们的值都必须为字符串类型。
好了,看到这里,我们知道“biggerSchema”定义了一种 JSON 规则,现在我们来看看,如下的两个 JSON 对象是否合法:
清单 21. 简单的 JSON
var biggerObj = {
"name" : "John Doe",
"born" : "1979-03-23T06:26:07Z",
"gender" : "male",
tuple:[1,"a",false],
"address" : {"street":"123 S Main St",
"city":"Springfield",
"state":"CA"}
};
|
这里我们看到,如果按照清单 1 中的 schema 来验证,这里"name"为字符,且长度大于 5;"born"为字符串,"gendar"的值符合要求,"tuple"为数组,且数组内容也满足条件,"address"为对象,且其内容也符合要求,所以该 JSON 对象(biggerObj)是合法的。
如何验证它是否合法呢?这个时候 Dojo 的 JSON 包的 schema 接口就派上用场了:
清单 22. JSON 验证
var valid = dojox.json.schema.validate(biggerObj,biggerSchema).valid Console.log(valid)--- true |
通过简单的一个 API,就可以完成复杂的验证工作,对于我们在平时日常 web 开发中可能拿到的所有 JSON 变量,用这种方式做数据检测和验证是最为便捷和安全的,推荐大家多多采用。
再来看看如下的另一个 JSON 对象:
清单 23. 简单的 schema
var invalidBiggerObj = {
"name" : "John Doe",
"born" : false,
"gender" : "mal",
"address" : {"street":"123 S Main St",
"city":"Springfield",
"state":"CA"}
};
|
细心的读者可能已经看到,这里的"gender"的值为"mal",不是定义中说的"male"和"female"其中之一,所以 validate 方法的返回值就是 false。
再来看看如下一种 schema:
清单 24. Disallow schema
var schema={
disallow: [{maxLength:4}]
}
|
这个 schema 看上去很简单,其实它就是定义:如果所验证的对象不超过 4 个字符,就不合法。(注意:schema 不是仅仅只能验证 JSON,它是和任何类型的数据,只不过 JSON 上用的比较广泛),其验证结果可以参考如下代码:
清单 25. 字符串验证
dojox.json.schema.validate("hello", schema).valid--- false
dojox.json.schema.validate("hi", schema).valid--- true
|
可见,"hello"有 5 个字符,超过之前的 4 位({maxLength:4})的限制,所以返回 false,不合法。而"hi"是满足要求的。
还有数字的验证:
清单 26. Decimal schema
var schema={
maxDecimal: 4
}
|
很简单,它就是定义小数位数不能超过 4 位,其验证结果可以参考如下代码:
清单 27. 数字验证
dojox.json.schema.validate(4.44, schema).valid--- true dojox.json.schema.validate(4.444444, schema).valid--- false |
Dojo 的 JSON 验证包还可以验证数组:
清单 28. 数组 schema
var schema={
items: [{type:"string"},{type:"number"}]
};
|
数组有两个元素,第一个为字符串,第二个为数字。其验证结果可以参考如下代码:
清单 29. 数组验证
dojox.json.schema.validate(4.44, schema).valid--- true dojox.json.schema.validate([22,22], schema).valid--- false |
很显然,"[22, 22]" 均为数字,不合法。
再来看一个简单的验证 schema:
清单 30. 纯类型 schema
var schema={
type: ["string", "number"]
};
|
这个专门用于验证输入值必须为字符串或数字。其验证结果可以参考如下代码:
清单 31. 纯类型验证
dojox.json.schema.validate(22, schema).valid--- true
dojox.json.schema.validate("hi", schema).valid--- true
dojox.json.schema.validate(null, schema).valid--- false
dojox.json.schema.validate({foo:"bar"}, schema).valid--- false
|
所以,可以看出,空值和对象在这里都无法通过验证。
再来看一个稍微复杂一点的 schema:
清单 32. 对象 schema
var schema = {type:[
{type:"object", properties:{name:{type:"string"}, id:{type:"integer"}},
additionalProperties:false},
{type:"object", properties:{brand:{type:"string"}, id:{type:"integer"}},
additionalProperties:false}]
};
|
这里它表示,所验证的内容为:两种对象类型任选其一,符合其中一个即可。其验证结果可以参考如下代码:
清单 33. 对象验证
1. dojox.json.schema.validate({name:"Bill", id:3}, schema).valid--- true
2. dojox.json.schema.validate({brand:"Dojo", id:3}, schema).valid--- true
3. dojox.json.schema.validate({foo:"bar"}, schema).valid--- false
4. dojox.json.schema.validate({foo:"bar", brand:"Dojo", id:3}, schema).valid--- false
5. dojox.json.schema.validate("a string", schema).valid--- false
|
"additionalProperties: false" 表示这里不能添加额外属性,任何缺失(示例 3),多余添加(示例 4),以及非对象类型(示例 5)都不合法。
最后大家看看这个 schema:
清单 34. 不合法的 schema
var schema = {
properties: { foo:"string"}
};
|
这其实是一个不合法的 schema,它没有定义 type 为 object,就直接定义其键"foo"必须为字符串,这种写法不符合 schema 的定义规范。
这篇文章介绍了 Dojo 开发中关于 JSON 的工具包,从查询(Query),引用以及验证三个角度阐述了 JSON 包的主要功能,基于实例源代码,阐述了查询接口,引用接口和定义验证接口的很多初级和高级功能及使用方式等等,同时,也强调了一些不合法的使用方式。这些工具包的使用可以大大提高我们的开发效率,提高我们代码的质量,我们可以在开发过程中多关注一下,以尽可能多的完善我们的 Web 应用。
学习
-
JSON 介绍:关于 JSON 比较全面的介绍和案例。
-
Dojo 校园文档主页:Dojo 中控件的比较完全的 API 文档主页,包括 Dojo,Dijit,Dojox 等。
-
Dojo 官方文档主页:Dojo 官方的很多支持 Ajax 应用程序开发的组件的文档。
- “JSON 入门指南”(developerWorks,2008 年 8 月):JSON 即 JavaScript Object Natation,它是一种轻量级的数据交换格式,非常适合于服务器与 JavaScript 的交互。本文将快速讲解 JSON 格式,并通过代码示例演示如何分别在客户端和服务器端进行 JSON 格式数据的处理。
-
developerWorks Web development
专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
-
developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
-
developerWorks Web 2.0 资源中心,这是有关 Web 2.0 相关信息的一站式中心,包括大量 Web 2.0 技术文章、教程、下载和相关技术资源。您还可以通过 Web 2.0 新手入门 栏目,迅速了解 Web 2.0 的相关概念。
- 查看 HTML5 专题,了解更多和 HTML5 相关的知识和动向。
讨论
- 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
