owned this note
owned this note
Published
Linked with GitHub
資料驗證好幫手 - JSON Schema Validator
===
文件大綱:
* [JSON schema 簡介及作用](#intro) - 告訴你什麼是JSON Schema
* [JSON schema 詳細介紹](#instruction) - 有完整的文件告訴你JSON Schema中每個propety的作用
* [JSON Schema範例](#sample) - 就像看例句學英文一樣,看範例或許比看文件更好懂
* [JSON Schema Validator](#validator) - [Ajv套件](#ajv)
* [Jquery-Validation](#jquery-validation) - 直得參考的的套件
## <a id="intro">JSON schema 簡介及作用</a>
直接先看一下JSON schema的範例,暖個身
```json
{
"title":"產品資訊",
"description":"產品包含商品標號、商品名稱及價錢",
"type":"object",
"properties":{
"pid":{
"description":"產品的id,是product id的縮寫",
"type":"integer"
},
"name":{
"description":"產品名稱",
"type":"string",
"maxLength": 10
},
"price":{
"type":"number",
"exclusiveMinimum":0
}
},
"required":[
"pid",
"name",
"price"
]
}
```
JSON Schema就是JSON資料結構的規範,規定一個JSON資料該有哪些資料,包含規定型態、大小範圍等。JSON Schema有官方規範,可以在[http://json-schema.org](http://json-schema.org)上查看,目前已出到draft-07。
這份文件要介紹的 [Ajv (Another JSON Schema Validator)](https://github.com/epoberezkin/ajv) 套件會支援 draft-04/06/07 這幾個版本。
JSON Schema 功用如下:
* 用於構建人機可讀的文件:
- JSON Schema 可以讓系統讀取,同時也是一個讓人一目瞭然的文件檔,一舉兩得。
* 用於生成模擬資料:
- 有了JSON Schema,可以自動生成符合規定的資料讓測試程式使用。
* 用於資料驗證:
- 不需要再寫程式來判斷資料長度跟內容了,所有的邏輯都可以移植到JSON Schema中維護,最後我們會使用ajv這個工具來使用JSON schema進行驗證。
---
## <a id="instruction">JSON schema 詳細介紹</a>
網路上已經有文件把9成的功能介紹完了,廢話不多說直接看[JSON Schema 辭典](http://xaber.co/2015/10/20/JSON-schema-%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B%E6%96%87%E6%A1%A3/)吧!
其他教學文件:
* [JSON Schema 基本概念教學](http://taobaofed.org/blog/2016/01/25/jsonschema/)
* [JSON Schema 參考書](http://imweb.io/topic/57b5f69373ac222929653f23)
* [ajv的keyword列表](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md)
---
## <a id="sample">JSON Schema範例</a>
比起看文件,大家更愛範例,所以在這邊提供幾個範例,剩下的可以去上面的文件看。
資料是數字(在這邊成為n), 3 <= n < 9 且 n要是3的倍數
```json=
{
"type": "number",
"minimum": 3,
"exclusiveMaximum": 9,
"multipleOf": 3
}
```
* `exclusiveMinimum`與`exclusiveMaximum`在draft-04以前是boolean,但在draft-06以後則變成數字。
---
字串長度為5~10
```json=
{
"type": "string",
"minLength": 5,
"maxLength": 10,
}
```
---
符合正規表示式的字串,輸入的字串必須符合身分證字號的格式
```json=
{
"title": "ID card number",
"type": "string",
"pattern": "^[A-Z]{1}[0-9]{9}$"
}
```
---
符合ipv4的的字串,還有`email`跟`date-time`等format
```json=
{
"type": "string",
"format":"ipv4"
}
```
---
陣列長度2~5,而且必須都是數字,並不得有重複的數字
```json=
{
"type": "array",
"items": {
"type": "number"
},
"minItems": 2,
"maxItems": 5,
"uniqueItems": true
}
```
---
陣列內只能有指定的[ `整數` , `DC或Marvel` ],不可以有其他東西
```json=
{
"type": "array",
"items": [
{
"description": "粉絲的年紀",
"type": "integer"
},
{
"type": "string",
"enum": ["DC", "Marvel"]
}
],
"additionalItems": false
}
```
* 在沒有additionalItems的情況下,[ `整數` , `DC或Marvel` , `這邊要放多少東西都可以` ],additionalItems可以指定type等相關資訊,代表多出來的陣列內容必須符合additionalItems的限制。
---
Object物件中,最多只能有5個property,一定要有`nickname`,當有`carMileage`時,就一定要有`carBrand`,反之則不需要,`carBrand`只能填bmw或benz,除了原本定義的property之外,其他的property都得是string型態。
```json=
{
"type":"object",
"maxProperties":5,
"properties":{
"nickname":{ "type": "string" },
"carBrand":{ "enum": ["benz", "bmw"] },
"carMileage":{ "type":"number" }
},
"required": ["nickname"],
"dependencies":{ "carMileage": ["carBrand"] },
"additionalProperties":{ "type":"string" }
}
```
`oneOf`代表 **只能** 符合其中一項,也就是這個數字必須是5或3的倍數,但不能是15的倍數,另外還有`anyOf`跟`allOf`以及`not`可以使用
```json=
{
"type": "number",
"oneOf": [
{ "multipleOf": 5 },
{ "multipleOf": 3 }
]
}
```
---
使用refs引用定義好的definitions,先定義好正整數後直接引用
```json=
{
"type": "array",
"items": { "$ref": "#/definitions/positiveInteger" },
"definitions": {
"positiveInteger": {
"type": "integer",
"exclusiveMinimum": 0
}
}
}
```
definitions相當於引用自己建立的變數的感覺,厲害的是$refs可以引用外部檔案的schema,像是json-schema.org提供的官方範例[geo.json](http://json-schema.org/example/geo.json#)
```json=
{
"$ref": "http://json-schema.org/geo.json#"
}
```
---
[draft-04更新到draft-06時多出的功能](http://json-schema.org/draft-06/json-schema-release-notes.html):
* [contains](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#contains) - `{ "contains": { "type": "integer" } }` means that any array with at least one integer, any non-array is matched
* [propertyNames](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#propertynames) - property名稱必須符合規範
* [const](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#const) - 比對不同property的值是否相同
[draft-06更新到draft-07時多出的功能](http://json-schema.org/draft-07/json-schema-release-notes.html):
* [if/then/else](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#ifthenelse)
if-else的範例
```json=
{
"type": "integer",
"minimum": 1,
"maximum": 1000,
"if": { "minimum": 100 },
"then": { "multipleOf": 100 },
"else": {
"if": { "minimum": 10 },
"then": { "multipleOf": 10 }
}
}
```
符合規定的資料: 1, 5, 10, 20, 50, 100, 200, 500, 1000
不符合規定的資料:
* -1, 0 (<1)
* 2000 (>1000)
* 11, 57, 123 (any number with more than one non-zero digit)
* non-integers
---
### \$schema與\$id到底是什麼?
The "\$schema" keyword
* [$schema](https://spacetelescope.github.io/understanding-json-schema/reference/schema.html)代表你寫的JSON Schema文件遵循的規範是哪一個版本。最新版的draft的schema是`http://json-schema.org/schema#`,目前如果你打開來看的話會是draft-07,如果想指定成draft-06版的話可以寫成`http://json-schema.org/draft-06/schema#`
The "\$id" keyword - schemaId
* 官方文件提到的[schemaId](https://github.com/epoberezkin/ajv#referenced-schema-options)
* \$id 是拿來定義這個schema的獨立編號,必須是uri。
[http://json-schema.org/example/geo.json#](http://json-schema.org/example/geo.json#)中的\$id就是`"http://json-schema.org/geo"`,別人使用$ref想遠端過來取用這個schema就是對應這個\$id,object中的property也可以有\$id,有如path的概念
* 來看以下範例,如果想取用內部的定義則用`#/definitions/B`,取用外部的話則用`http://example.com/other.json`,詳細教學看[這邊](https://tools.ietf.org/html/draft-wright-json-schema-01#section-9.2)
```json=
{
"$id": "http://example.com/root.json",
"definitions": {
"A": { "$id": "#foo" },
"B": {
"$id": "other.json",
"definitions": {
"X": { "$id": "#bar" },
"Y": { "$id": "t/inner.json" }
}
},
"C": {
"$id": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"
}
}
}
```
* [其他\$id參考文件](https://spacetelescope.github.io/understanding-json-schema/structuring.html#the-id-property)
---
## <a id="validator">JSON Schema Validator</a>
JSON Schema Validator就是專門來驗證JSON的工具,將要比對的資料拿去跟schema比對,馬上就知道資料符不符合規定,並且能夠得知是哪個資料的哪個環節不符合規定,相當方便,目前星星數最多,而且還有在維護的就屬[Ajv(Another JSON Schema Validator)](https://github.com/epoberezkin/ajv)莫屬了,以前我用過的JSON Schema Validator是[tv4](https://github.com/geraintluff/tv4)。
另外有一個[react-jsonschema-form](https://github.com/mozilla-services/react-jsonschema-form),只要在`<form>`物件中放入schema跟data,它就能自動生成Bootstrap表單,它的介紹語是這樣寫的"A simple React component capable of building HTML forms out of a JSON schema and using Bootstrap semantics by default."
react-jsonschema-form 程式範例
```jsx=
import React, { Component } from "react";
import { render } from "react-dom";
import Form from "react-jsonschema-form";
const schema = {
title: "Todo",
type: "object",
required: ["title"],
properties: {
title: {type: "string", title: "Title", default: "A new task"},
done: {type: "boolean", title: "Done?", default: false}
}
};
const log = (type) => console.log.bind(console, type);
render((
<Form schema={schema}
onChange={log("changed")}
onSubmit={log("submitted")}
onError={log("errors")} />
), document.getElementById("app"));
```
但因為綁定Bootstrap有一點死,所以這邊還是選用比較有彈性的ajv。
---
## <a id="ajv">Ajv: Another JSON Schema Validator</a>
注意:完整的文件可以直接去[github](https://github.com/epoberezkin/ajv)上看,這邊會提到比較常用到的內容,或是我有用過的內容。[[npm ajv 連結](https://www.npmjs.com/package/ajv)]
這邊有模擬在Nodejs上面執行ajv的[連結](https://npm.runkit.com/ajv),可以進去試用看看。
我們已經學會了JSON Schema了,現在直接使用Ajv來比對schema跟data就能驗證資料了,看看下面的範例吧!符合條件的話ajv.validate就會回傳true,反之則false
```javascript=
var ajv = Ajv();
var schema = {
"type": "number",
"oneOf": [
{ "multipleOf": 5 },
{ "multipleOf": 3 }
]
};
var data10 = 10;
var data15 = 15;
console.log(ajv.validate(schema, data10)); // true
console.log(ajv.validate(schema, data15)); // false
```
---
如果想知道錯在哪裡,我們就輸出`ajv.errors`,也是直接看下面範例。
我們在字串長度應該是5的schema中輸入長度是4的字串
```javascript=
var ajv = Ajv();
var schema = {
"type": "string",
"minLength": 5
};
var data = "four";
var valid = ajv.validate(schema, data);
if (!valid) console.log(ajv.errors);
```
資料長度不符合規定時Ajv回傳的錯誤訊息格式如下,大致上看得懂,我們去看下個範例後再解說這些參數是幹嘛的。
```json=
[
{
"keyword":"minLength",
"dataPath":"",
"schemaPath":"#/minLength",
"params":{
"limit":5
},
"message":"should NOT be shorter than 5 characters"
}
]
```
---
這次的範例有兩個錯誤,分別是`2016-12-99`不符合`date`格式,以及`fruit`欄位應該要是字串卻填入了數字,如果要顯示超過一個以上的error就必須在[Options](https://github.com/epoberezkin/ajv#options)欄位加上`{ allErrors: true }`,否則它只會回傳第一個error。
```javascript=
var ajv = new Ajv({ allErrors: true });
var schema = {
"type": "object",
"properties": {
"purchaseDate": { "format": "date" },
"foods": {
"type": "object",
"properties": {
"fruit": { "type": "string" }
},
},
}
};
var data = {
"purchaseDate": "2016-12-99",
"foods": {
"fruit": 5566
}
};
var valid = ajv.validate(schema, data);
if (!valid) console.log(ajv.errors);
```
回傳了兩個錯誤
```json=
[
{
"keyword":"format",
"dataPath":".purchaseDate",
"schemaPath":"#/properties/purchaseDate/format",
"params":{
"format":"date"
},
"message":"should match format 'date'"
},
{
"keyword":"type",
"dataPath":".foods.fruit",
"schemaPath":"#/properties/foods/properties/fruit/type",
"params":{
"type":"string"
},
"message":"should be string"
}
]
```
想要知道error object的架構,可以看[Validation errors](https://github.com/epoberezkin/ajv#validation-errors),我把裡面的部分內容節錄在下方。
* `keyword`: validation keyword.
* `dataPath`: the path to the part of the data that was validated. By default dataPath uses JavaScript property access notation (e.g., ".prop[1].subProp"). When the option jsonPointers is true (see Options) dataPath will be set using JSON pointer standard (e.g., "/prop/1/subProp").
* `schemaPath`: the path (JSON-pointer as a URI fragment) to the schema of the keyword that failed validation.
* `params`: the object with the additional information about error that can be used to create custom error messages. [[params文件](https://github.com/epoberezkin/ajv#error-parameters)]
* `message`: the standard error message (can be excluded with option messages set to false).
---
[API列表](https://github.com/epoberezkin/ajv#api) - 接在ajv後面的api都寫在這。
**`.compile(Object schema)`**
validating function and cache the compiled schema for future use,比起每次都用`ajv.validate()`的執行速度還快,官方文件也說這是`The fastest validation call`
---
**`.errorsText([Array<Object> errors [, Object options]])`**
`ajv.errorsText(ajv.errors)`這樣的寫法可以把error object轉換成一個錯誤訊息字串,
remote schemas have to be added with addSchema or compiled to be available
---
**`.addFormat(String name, String|RegExp|Function|Object format)`**
使用`addFormat`定義客製化的format,可以參考下面的範例
```javascript=
var ajv = new Ajv().addFormat('cellphone', '^09[0-9]{2}-[0-9]{3}-[0-9]{3}$');
var schema = {
"format": "cellphone"
};
var data = '0912-345-678'; // format have to be 09XX-XXX-XXX
var validate = ajv.compile(schema);
console.log(validate(data)); // true
```
---
**`.addKeyword(String keyword, Object definition)`**
自訂keyword
```javascript=
ajv.addKeyword('range', {
type: 'number',
compile: function (sch, parentSchema) {
var min = sch[0];
var max = sch[1];
return parentSchema.exclusiveRange === true
? function (data) { return data > min && data < max; }
: function (data) { return data >= min && data <= max; }
}
});
var schema = { "range": [2, 4], "exclusiveRange": true };
var validate = ajv.compile(schema);
console.log(validate(2.01)); // true
console.log(validate(3.99)); // true
console.log(validate(2)); // false
console.log(validate(4)); // false
```
---
**`.addSchema(Array<Object>|Object schema [, String key])`**
`compile`跟`addSchema`非常像,其實我也分不太出來為啥要分兩個,
[addSchema vs schemas](https://github.com/epoberezkin/ajv#combining-schemas-with-ref)
直接傳schema
```javascript
var validate = jv.addSchema(schema)
```
傳schema並給予一個key值
```javascript
var valid = ajv.addSchema(schema, 'mySchema').validate('mySchema', data);
```
除了`addSchema`的話,也可以在宣告`Ajv`時直接assign`schemas`
```javascript
var ajv = new Ajv({schemas: [schema1, schema2]});
```
schemas: an array or object of schemas that will be added to the instance. In case you pass the array the schemas must have IDs in them
---
**`.addMetaSchema(Array<Object>|Object schema [, String key])`**
* 這裡有官方對於[Meta Schema](http://json-schema.org/specification.html)的介紹,meta-schema是就是JSON Schema的範本,目前最新的meta-schema是draft-07。
至於為何要addMetaSchema我還不是非常清楚
---
**`.getSchema(String key)`**
只透過uri來取得Schema
```javascript
ajv.getSchema('http://example.com/schemas/schema.json')
```
---
## <a id="jquery-validation">Jquery-Validation - 直得參考的的套件</a>
我認為JSON-validator必須要有良好的message回傳機制才實用,所以我找到了一個我個人認為設計蠻好的套件[jquery-validation](https://github.com/jquery-validation/jquery-validation),因為是jquery所以不太適合套用到reactjs專案,但是可以試著引用它的設計模式來打造一個適合自己專案的validator。可以看看Youtube上的[jQuery Validation Plugin 播放清單](
https://www.youtube.com/watch?v=xNSQ3i-BWMo&list=PL5ze0DjYv5DaAm5eC2chbTK1Y6uoTUtZ9)來了解如何使用。
基本上它就是將驗證的rule與要回傳的message分成兩個property互相對應