利⽤JsonSchema校验json数据内容的合规性(转)
原⽂地址:
背景:
复杂的AJAX应⽤程序可以与数百个不同的JSON服务进⾏交互,因此,引⼊对客户端验证的需求。
在处理校验问题⽅⾯有着很多的⼯具,但是通常可以将它们归为以下⼏类:
* 判断数据是否已被正确格式化
* ⼿动检查形式有误的数据并尝试纠正
* ⼿动检查形式有误的数据并将有误数据丢弃
* ⾃动检查形式有误的数据
JSON schema是⼀个帮助你定义、校验甚⾄是修复json数据格式的解决⽅案。它定义了⼀整套规则,允许我们通过定义⼀个schema(本⾝也是JSON)来描述⼀个JSON串的数据格式。它有如下优点:
* 描述你现有的JSON数据的格式;
* 清晰的、⼈类/机器可读的⽂档结构;
* 完全的结构校验,尤其适⽤于⾃动测试或验证客户端提交的数据格式。
下⾯为⼀个定位信息的json schema例⼦
//json传输值
{
"data" : {
"id" : 851,
"detail" : "琴千线长征路-万盛南路附近",
"area" : 9480,
"province" : "浙江省",
"parentArea" : 2819,
"lng" : 120.32438,
"district" : "东阳市",
"lat" : 29.136176,
"city" : "⾦华"
}
}
//定位接⼝返回值的JSON schema
{
"type" : "object",
"properties" : {
"data" : {
"type" : "object",
"properties" : {
"id" : {
"type" : "integer",
"minimum": 0
},
"detail" : {
"type" : "string"
},
"area" : {
"type" : "integer"
},
"province" : {
"type" : "string",
"pattern" : "^(北京市|天津市|....|浙江省)$"
},
"parentArea" : {
"type" : "integer"
},
"lng" : {
"type" : "number",
"minimum" : 73,
"maximum" : 135
},
"district" : {
"type" : "string"
},
"lat" : {
"type" : "number",
"minimum" : 4,
"maximum" : 54
},
"city" : {
"type" : "string"
}
},
"required" : [
"id",
"detail",
"area",
"province",
"parentArea",
"lng",
"district",
"lat",
"city"
]
}
},
"required" : [
"data"
]
}
可以看出:
1、json schema 本⾝也是⼀个json串
2、每个schema可以描述⼀个json实例,并且该json实例⾥每⼀个节点都可以⽤⼀个schema来描述,因此schema与json⼀样,本⾝也是⼀个层级结构,⼀个schema中可能嵌套着另外若⼲层schema
3、json schema 定义的检查规则以数据格式验证为主(字段存在性、字段类型),并可以⽀持⼀些简单的数据正确性验证(例如数值范围、字符串的模式等),但不能进⾏复杂的逻辑校验(例如进价必须⼩于售价等)。
JS JSON Schema库
表1中简要概述了4个JSON Schema库的特性
表 1. 针对 JavaScript 的 JSON Schema 验证库
库(作者)草案版本⽀持库的⼤概规模
JSV: JSON Schema 验证器 (Gary Court)draft-01、draft-02 和 draft-03120KB
json-schema (Kris Zyp)draft-0310KB(需要 CommonJS)
dojox.json.schema (Kris Zyp)draft-0210KB(需要 Dojo)
schema.js (Andreas Kalsch)draft-02(部分)10KB(需要 CommonJS)
基于 Dojo 的应⽤程序可能会使⽤ dojox.json.schema 库,因为该库包含在⼯具箱中。⽀持多个版本的(草案)标准的应⽤程序可能会使⽤JSV。
dojox.json.schema 看上去像是 json-schema 的⼀个分⽀,所以它们的⽤法⾮常相似。schema.js 只实现 draft-02 的⼀个⼦集。所以主要关注的是使⽤ dojox.json.schema 和 JSV 。
1. dojox.json.schema的使⽤
<html>
<head>
<title>dojox.json.schema</title>
<script src="leapis/ajax/libs/dojo/1.7.0/dojo/dojo.js"
421事件内容
type="text/javascript"></script>
<script type="text/javascript">
require(["dojox/json/schema"], function() {
// Object to validate
var successObj = {
"foo" : "bar"
};
var failureObj = {
"foo" : 1234
};
// Schema
var schema = {
"type": "object",
"properties" : {
"foo" : {
"type" : "string"
}
}
};
//var result = dojox.json.schema.validate(failureObj, schema);
var result = dojox.json.schema.validate(successObj, schema);
// Check results
if (result.valid) {
alert("Object is valid");
} else {
var errorArr = s;
alert("property : " + errorArr[0].property + "\nmessage :  "
+ errorArr[0].message);
}
});
</script>
</head>
<body>
Hello, World!
</body>
</html>
dojox.json.schema这种⽅法只需要引⼊⼀个js包,不过必须是线上的,将dojo那个js下载下来后就报错不能执⾏。
2. JSV的使⽤
<head>
<title>JSV</title>
<!-- <script src="raw.github/garycourt/JSV/master/lib/uri/uri.js" type="text/javascript"></script>
<script src="raw.github/garycourt/JSV/master/lib/jsv.js" type="text/javascript"></script>
<script src="raw.github/garycourt/JSV/master/lib/json-schema-draft-03.js" type="text/javascript"></script> -->
<script src="js/uri.js" type="text/javascript"></script>
<script src="js/jsv.js" type="text/javascript"></script>
<script src="js/json-schema-draft-03.js" type="text/javascript"></script>
<script type="text/javascript">
// Object to validate
var successObj = {
"foo" : "bar"
};
var failureObj = {
"foo" : 1234
};
// Schema
var schema = {
"type": "object",
"properties" : {
"foo" : {
"type" : "string"
}
}
};
var env = ateEnvironment("json-schema-draft-03");
// validate
var result = env.validate(successObj, schema);
if (s.length === 0) {
alert("Object is valid");
} else {
var errorArr = s;
alert("uri : " + errorArr[0].uri + "\nmessage :  "
+ errorArr[0].message);
}
</script>
</head>
<body>
Hello, World!
</body>
</html>
JSV这种⽅法,需要导⼊3个js包,这是必须下载后才能使⽤。
JSV 在 errors 数组中提供了⼀些⾼级故障信息。每个错误可能包含以下属性:
* message:⼈类可读的错误消息。
* uri:失败对象所在位置的 URI。
* schemaUri:引起故障的模式的所在位置的 URI。
* Attribute:引起故障的模式约束。
* Details:包含更多详细信息的⾃由格式数组,⽐如预期值。
使⽤对⽐:
dojox.json.schema只需要引⼊⼀个js包,基于 Dojo 的应⽤程序可能会使⽤ dojox.json.schema 库,因为
该库包含在⼯具箱中。校验的时候也只需要⼀⾏代码即可:var result = dojox.json.schema.validate(successObj, schema); 其中successObj为传⼊的JSON串,schema为校验规则。
JSV需要引⼊三个js包,JSV⽀持draft-01,draft-02,draft-03三种草案,⽀持多个版本的(草案)标准的应⽤程序可能会使⽤ JSV。校验的时候需要根据草案创建环境,然后再进⾏校验。var env = ateEnvironment(“json-schema-draft-03”);
var result = env.validate(successObj, schema); 其中successObj为传⼊的JSON串,schema为校验规则。JSV在errors数组中提供了⼀些⾼级的故障信息,包括message:可读的错误信息;uri:失败对象所在位置的URI;schemaUri:引起故障的模式所在位置的URI;Attribute:引起故障的模式约束;Details:包含更多详细信息的⾃由格式数组,如果预期值。
性能对⽐:
⼀共执⾏50次,成功和失败分开执⾏,每种情况执⾏25次。然后记录下每次的执⾏时间,执⾏10次,取平均值。
dojox.json.schema:0.52, 4.28, 3.54, 4, 3.82, 3.64, 3.76, 4.12, 4.16, 5.6
JSV:4.5, 3.96, 3.88, 3.82, 3.98, 3.96, 3.9, 3.8, 4.1, 4.04
json schema类型每次执⾏时间(ms)
dojox.json.schema  3.744
JSV  3.994
发现时间相差不多,JSV由于js包在本地,所以每次时间⽐较稳定;dojox.json.schema由于需要从⽹络上去加载js包,导致执⾏时间有时会波动很⼤。整体来说,就执⾏过程,dojox.json.schema要快不少。
Java JSON Schema库
表2给出了两种java中使⽤的JSON Schema库
库名称地址⽀持草案
fge draft-04 draft-03
everit draft-04
建议:
如果在项⽬中使⽤了jackson json,那么使⽤fge是⼀个好的选择,因为fge就是使⽤的jackson json。
如果项⽬中使⽤的是org.json API,那么使⽤everit会更好。
如果是使⽤以上两个库以外的库,那么就使⽤everit,因为everit会⽐fge的性能好上两倍。
fge的使⽤:
maven配置
<dependency>
<groupId>com.</groupId>
<artifactId>jackson-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.github.fge</groupId>
<artifactId>json-schema-validator</artifactId>
<version>2.2.6</version>
</dependency>
测试代码:
@Test
public void testJsonSchema1() {
JsonNode schema = readJsonFile("src/main/resources/Schema.json");
JsonNode data = readJsonFile("src/main/resources/failure.json");
ProcessingReport report = JsonSchemaFactory.byDefault().
getValidator().validateUnchecked(schema, data);
Assert.assertTrue(report.isSuccess());
}
private JsonNode readJsonFile(String filePath) {
JsonNode instance = null;
try {
instance = new JsonNodeReader().fromReader(new FileReader(filePath));
} catch (IOException e) {
e.printStackTrace();
}
return instance;
}
真正的调⽤只有⼀⾏代码,需要传⼊验证规则和数据。分别有validate和validateUnchecked两种⽅法,区别在于validateUnchecked⽅法不会抛出ProcessingException异常。
还可以从字符串中读取json,代码如下:
@Test
public void testJsonSchema2() {
String failure = new String("{\"foo\":1234}");
String Schema = "{\"type\": \"object\", \"properties\" : {\"foo\" : {\"type\" : \"string\"}}}";
ProcessingReport report = null;
try {
JsonNode data = JsonLoader.fromString(failure);
JsonNode schema = JsonLoader.fromString(Schema);
report = JsonSchemaFactory.byDefault().getValidator().validateUnchecked(schema, data);
} catch (IOException e) {
e.printStackTrace();
}
//Assert.assertTrue(report.isSuccess());
Iterator<ProcessingMessage> it = report.iterator();
while (it.hasNext()) {
System.out.());
}
}
其中ProcessingReport对象中维护了⼀共迭代器,如果执⾏失败(执⾏成功时没有信息),其提供了⼀些⾼级故障信息。每个错误可能包含以下属性:
* level: 错误级别(应该就是error)
* schema:引起故障的模式的所在位置的 URI
* instance:错误对象
* domain:验证域
* keyword:引起错误的约束key
* found:现在类型
* expected:期望类型
以上代码的json信息为:
failure.json :  {"foo" : 1234}
Schema.json :
{
"type": "object",
"properties" : {
"foo" : {
"type" : "string"
}
}
}
执⾏错误信息为:
error: instance type (integer) does not match any allowed primitive type (allowed: ["string"])
level: "error"
schema: {"loadingURI":"#","pointer":"/properties/foo"}
instance: {"pointer":"/foo"}
domain: "validation"
keyword: "type"
found: "integer"
expected: ["string"]
everit的使⽤:
maven配置(获取最新版本)
<dependency>