javascript 資料型態/結構驗證庫 : Skeletons
當初專案需要將資料以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 typesymbol
建立一個 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 } }) ) 複製程式碼