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
}
})
)
结语
就先介绍到这,这个库比较像是开发阶段、测试使用的,可确保资料的结构、型态符合自己的要求,避免后续程序出错,
并希望用直觉简单的方式就能定义复杂的结构。
希望对大家能有所帮助,谢谢。