Skeletons 是一個簡單直覺的純JS資料驗證庫
前言
當初專案需要將資料以JSON格式儲存在本地端,萬一資料結構出了問題或是不符合預其,後面程式都會出問題,因此想寫一個簡單直覺的純JS資料驗證方法,並開源到npm上。
希望對大家有幫助。喜歡可以給個星:) 有任何討論都歡迎。
原始碼
Javascript 型態
先來介紹一下Javascript有趣的資料型態,如有錯誤請幫忙提出修正~
JS 共有七種資料型態
其中包含六種 Primitive types :
- Boolean
- Null
- Undefined
- Number
- String
- Symbol
和 Object
特別的是 Function 廣義來說也是屬於物件。
我們可以用 typeof
來檢查型態 (回傳一個字串)
typeof 1 // `number`
typeof "" // `string`
typeof true // `boolean`
typeof undefined // `undefined`
typeof null // `object`
typeof Symbol // `symbol`
typeof function(){ } // `function`
typeof {} // `object`
複製程式碼
等等,你有沒有發現什麼端倪?我們一個一個看。
Number
一般我們會直接定義變數:
let a = 1
複製程式碼
也可以用 function 定義
let a = Number(1)
typeof a // `number`
複製程式碼
但如果加上 new
,則會建立一個Number物件
let a = new Number(1)
typeof a // `object`
複製程式碼
NaN
也是屬於Number
typeof NaN // `number`
複製程式碼
如果要判斷變數a
是可計算的數字且不是NaN
,以下是行不通的
if(typeof a===`number` && a!==NaN) //... a !== NaN 永遠是 true
複製程式碼
因為 NaN
很特別,它不會等於任何值
// 以下通通都是 false !
NaN == 0
NaN == false
NaN == NaN
NaN === NaN
複製程式碼
你可能會想到用 boolean
來判斷 true
/false
if(typeof a===`number` && !a) // ...
複製程式碼
但是別忘了還有個 0
:
if(typeof a===`number` && !a && a !== 0) // ...
複製程式碼
當然最簡單的是用 isNaN
這個方法區分
if(typeof a===`number` && !isNaN(a)) // ...
複製程式碼
Boolean, String
和 Number 很像,注意用new的話會一樣是建立object。
let a = true // boolean
a = Boolean(true) //boolean
a = new Boolean(true) //object
複製程式碼
Undefined
也是一種premitive type
undefined
和 null
是沒有 funtion的 ,直接指定值就好
typeof undefined // `undefined`
複製程式碼
值得一提的是,雖然以下都是否定值 (false)
Boolean(0)
Boolean(false)
Boolean(``)
Boolean(undefined)
Boolean(null)
複製程式碼
但動態型別方面0, false, ``
是一夥的,
undefined, null
則是另外一個團體
0 == false // true
0 == `` // true
false == `` // true
undefined == null // true
undefined == false // false
undefined == 0 //false
null == `` //false
複製程式碼
Null
null也是一種type,但是。。。
typeof null // `object`
複製程式碼
沒錯,typeof
列印出來的是 `object`
上網查了一下,有些人說是JS當初設計的錯誤。
我們要判斷一個變數是物件的話可以這樣:
if(typeof a === `object` && a!==null) // ...
複製程式碼
Symbol
最後一個 premitive type symbol
建立一個 symbol:
let a = Symbol()
typeof a // `symbol`
複製程式碼
注意不能用 new
,會丟出錯誤
let a = new Symbol()
> Uncaught TypeError: Symbol is not a constructor
複製程式碼
Object
除了上面六種 primitive type,其他都歸類為物件
但特別的是 function,使用typeof
檢查會回傳function
字串:
typeof function(){} // `function`
複製程式碼
讓我們能很好的區別 function 和其他一般的物件
Skeletons
接下來要介紹這個庫了,有興趣的話可以先看看介紹。
請先記好上面 Javascript 原生定義的資料型別,因為這個庫的分類有些不一樣
Skeletons 中可定義的型別除了原本的七種JS型別,額外分出 array 和 function
原因是這兩個都是很常用的,將他們從物件特別區分出來。
使用方法
定義一個規則,使用 validate
來驗證資料
const rule = new Skeletons(schema)
rule.validate(data)
複製程式碼
Schema
定義規則需要傳入一個schema
,也就是你設想的資料結構以及形態
schema 可以有四種
1. premitive type function
共有四種可以用 (undefined和null是沒有function的,我們後面談如何定義)
- Number
- Boolean
- String
- Symbol
分別定義四種形態,使用上不用呼叫,直接傳入function
如下,定義一個型態為數字的schema
const schema = Number
複製程式碼
2. 使用 object literal
使用最值覺的 object literal 來定義一個物件 (注意,在Skeletons會排除array和function)的key
每個key都可指派另一個schema
如下定義了一個有 x, y 兩個鍵的物件,且兩個鍵的值都是數字型態
const schema = {
x: Number,
y: Number
}
複製程式碼
使用這種方式,讓你能夠輕易地定義結構較深的物件
const userSchema = {
name: String,
id: String,
VIP: {
code: Number,
details: {
type: String,
level: Number,
expired: Boolean
}
},
}
複製程式碼
3. array literal
使用array literal來定義有固定元素數量的array
const schema = [String, Number, Skeletons.Function()]
複製程式碼
4.呼叫Skeletons的靜態方法
- Skeletons.Number()
- Skeletons.String()
- Skeletons.Boolean()
- Skeletons.Null()
- Skeletons.Symbol()
- Skeletons.Any()
- Skeletons.Array()
- Skeletons.Object()
- Skeletons.Function()
- Skeletons.MapObject()
共有十種方法,分別代表是五種premitive type (不含undefined)、Object, 從物件中分出來的 Array, Function,以及特殊的Any(任何非undefined的型態) 和 MapObject
每種方法都接受一個options
物件當做引數,
且都可定義三個基本的property
-
options.required
type:
Boolean
default:
true
Skeletons對於任何
undefined
值都會認定為驗證失敗:new Skeletons({ a: Number }).validate({}) // data.a got undefined, validation filed 複製程式碼
如果要允許該層資料可以為
undefined
,設options.required
為 falsenew Skeletons({ a: Skeletons.Number({ required: false }) }) 複製程式碼
-
options.default
type: 任何
default:
undefined
有時後資料的預設值(或者空值)的型態可能會和資料有值的時後不一樣,比方說有人可能會用
null
來替代空的物件。new Skeletons(Skeletons.Object({ default: null })) 複製程式碼
-
options.validator
type:
Function
傳入一個function,回傳
true
/false
來驗證資料validator(value, data) 複製程式碼
該函式可接收兩個引數:
value
代表該層資料的值,data
代表整個資料以下這個例子,
value
等於120
,data
等於整個datasource
const datasource = { a: 120, b: 240 } new Skeletons({ a: Skeletons.Number({ validator: (val, data) => { // in this case, val = 120, data = datasource return val === data.b*2 } }), b: Number }) 複製程式碼
更多詳細的介紹可以參考檔案
驗證
驗證可分為
- 使用 console 列印出錯誤資訊
- 直接丟擲錯誤
如何設定可參考檔案
每次驗證後,可由warnings
屬性獲得錯誤資訊
const rule = new Skeletons(Number)
rule.validate(`1`)
rule.warnings // 一串array 包含所有錯誤資訊
複製程式碼
關於錯誤資訊可參考warnings
示例
接下來演示一些資料定義的範例
範例一 : 陣列
定義一個schema
代表不是NaN
的number
// ex: 1
const calcNum = Skeletons.Number({
allowNaN: false
})
複製程式碼
定義一個array
,每個元素是含有x
,y
屬性,值為非NaN
數字的物件
// ex: [{x: 1, y: 2}, {x: 3, y: 5}]
new Skeletons(
Skeletons.Array({
item: {
x: calcNum,
y: calcNum
}
})
)
複製程式碼
規定array
一定要有元素
// ex: [{x: 1, y: 2}, {x: 3, y: 5}]
new Skeletons(
Skeletons.Array({
validator: ary=>ary.length>0,
item: {
x: calcNum,
y: calcNum
}
})
)
複製程式碼
範例二 : 和其他資料比對
假設有一筆資料,當age
大於 18,grownup
等於true
代表已成年,反之則為false
const ex = {
age: 19,
grownup: true
}
複製程式碼
我們可以用 validator
來進行檢查
new Skeletons(
{
age: Number,
grownup: Skeletons.Boolean({
validator: (val, data) => val === data.age>=18
})
}
).validate(ex)
複製程式碼
範例三: 不限制物件的key
有時後物件作為一個類似map來除存對應的key,代表並沒有固定的屬性值和數量。這時可以使用 MapObject
例如 room
以房間的id當做key來mapping
const room = {
idkfd: {
name : `have fun`,
members: 4,
id: `idkfd`
},
ckclo: {
name : `My room`,
members: 2,
id: `ckclo`
},
ppqkd: {
name : `User0001`s room`,
members: 8,
id: `ppqkd`
}
}
複製程式碼
可這樣定義
new Skeletons(
Skeletons.MapObject({
keyValidator: (k, data)=> k === data[k].id,
item: {
name: String,
members: Number,
id: String
}
})
)
複製程式碼
結語
就先介紹到這,這個庫比較像是開發階段、測試使用的,可確保資料的結構、型態符合自己的要求,避免後續程式出錯,
並希望用直覺簡單的方式就能定義複雜的結構。
希望對大家能有所幫助,謝謝。