以校验整数为例为例:
tgc.compile({ "rule": "int" })
其中 int
是 TypeGuard 支持的一种基本类型,表示“整数”。
目前有如下基本类型可用:
Type | 取值范围 |
---|---|
string |
字符串 |
ascii_string |
ASCII 字符串 |
latin_string |
拉丁文字符串 |
hex_string |
十六进制字符串 |
int |
任意有符号整数 |
int8 |
8 位有符号整数 |
int16 |
16 位有符号整数 |
int32 |
32 位有符号整数 |
int64 |
64 位有符号整数 |
uint |
任意无符号整数 |
uint8 |
8 位无符号整数 |
uint16 |
16 位无符号整数 |
uint32 |
32 位无符号整数 |
uint64 |
64 位无符号整数 |
safe_int |
精度安全的有符号整数 |
safe_uint |
精度安全的无符号整数 |
float |
浮点数 |
ufloat |
无符号浮点数 |
decimal(M,D) |
M位有效数字,其中D位小数的精确十进制数(字符串) |
udecimal(M,D) |
M位有效数字,其中D位小数的精确无符号十进制数(字符串) |
number |
任意有效数值 |
numeric |
数值(可以是字符串) |
boolean |
布尔值 |
true |
布尔真值 |
false |
布尔假值 |
true_value |
逻辑真值 |
false_value |
逻辑假值 |
void |
不存在的值 |
optional |
同 void |
undefined |
同 void |
required |
存在的值(非 void ) |
any |
任意值(即不检查) |
array |
数组 |
struct |
结构类型(字典、对象、映射) |
null |
NULL值 |
部分基本类型支持增强校验
int
/float
/number
/numeric
这些数值类型支持取值范围校验。
int(-15,89) // 取值为 -15 ~ 89 的整数
int(,89) // 取值为 -∞ ~ 89 的整数
int(-15,) // 取值为 -15 ~ +∞ 的整数
float(-1000.123,-555) // 取值为 -1000.123 ~ -555 的浮点数
float(-1000.123,) // 取值为 -1000.123 ~ +∞ 的浮点数
float(,-1000.123) // 取值为 -∞ ~ -1000.123 的浮点数
number(123,255.23) // 取值为 123 ~ 255.23 的实数
number(-1000.123,) // 取值为 -1000.123 ~ +∞ 的实数
number(,-1000.123) // 取值为 -∞ ~ -1000.123 的实数
numeric(-1234,2222) // 取值为 -1234 ~ 2222 的数值
string
/hex_string
/latin_string
/ascii_string
这些字符串类型支持长度校验
string // 无限长度的字符串
string(1,256) // 长度为 1 ~ 255 的字符串
string(8) // 长度为 8 的字符串
string(8,) // 长度至少为 8 的字符串
- 数组表达式(Array)
所有基本类型都支持如下写法,以表示该类型的数组:
以字符串
string
类型为例。
string[] // 字符串数组
string[5] // 有 5 个元素的字符串数组
string[0,5] // 有 0 ~ 5 个元素的字符串数组
string[3,] // 至少有 3 个元素的字符串数组
string(1,8)[3] // 有 3 个元素的字符串数组,字符串长度为 1 ~ 8
- 映射表达式(Mapping)
所有基本类型都支持如下写法,以表示该类型的映射:
string{} // 值类型为字符串的 Key-Value 映射
int{} // 值类型为整数的 Key-Value 映射
- 混合使用
数组表达式、增强校验、映射表达式可以混合使用:
string(12, 32)[]
string(12, 32)[][]
string(12, 32)[5]
string(12, 32){}
string(12, 32){}{}
string(12, 32)[]{}
string(12, 32){}[3,]
校验器是一个以 "|"
开头的字符串,支持对值进行比较,格式为:
|<target> <rel> <...args>
target
是被校验的目标, rel
是校验的比较方式, args
是校验器的参数。
target
可以是如下值:
value
变量的值length
变量的值长度(字符串长度或者数组长度)string.length
字符串变量的长度(会先校验是否为字符串)array.length
数组变量的长度(会先校验是否为数组)- 任意基本类型
目前支持如下比较方式:
ge n
值大于或等于 ngte n
值大于或等于 ngt n
值大于 nle n
值小于或等于 nlte n
值小于或等于 nlt n
值小于 neq n
值等于 n(建议用直接量代替此校验器,更简单)ne n
值不等于 nbetween n m
值在 n~m 之间(包含 n 和 m)timesof n
值是 n 的倍数
例如:
/**
* 被校验的值必须大于等于 1 且小于等于 199
*/
tgc.compile({ "rule": "|value between 1 199" })
/**
* 被校验的值必须大于 1
*/
tgc.compile({ "rule": "|value gt 1" })
/**
* 被校验的值必须大于等于 1
*/
tgc.compile({ "rule": "|value ge 1" })
/**
* 被校验的值必须小于 1
*/
tgc.compile({ "rule": "|value lt 1" })
/**
* 被校验的值必须小于等于 1
*/
tgc.compile({ "rule": "|value le 1" })
/**
* 被校验的值必须不等于 1
*/
tgc.compile({ "rule": "|value ne 1" })
/**
* 被校验的值必须等于 1
*/
tgc.compile({ "rule": "|value eq 1" })
/**
* 被校验的值必须是整数且是 10 的倍数
*/
tgc.compile({ "rule": "|uint timesof 10" })
对于字符串类型,支持如下的匹配校验语法:
快捷符号写法:
Expression | Description |
---|---|
==text |
内容为 text 的字符串 |
=text |
内容为 text 的字符串 |
!=text |
内容不等于 text 的字符串 |
%=text |
内容为 text 的字符串(忽略大小写) |
%!text |
内容不等于 text 的字符串(忽略大小写) |
~=/abc/i |
内容匹配正则表达式 /abc/i 的字符串 |
~/abc/i |
内容匹配正则表达式 /abc/i 的字符串 |
~!/abc/i |
内容不匹配正则表达式 /abc/i 的字符串 |
?=text |
内容包含 text 的字符串 |
?!text |
内容不包含 text 的字符串 |
*=text |
内容包含 text 的字符串(忽略大小写) |
*!text |
内容不包含 text 的字符串(忽略大小写) |
^=text |
以 text 开头的字符串 |
^!text |
不以 text 开头的字符串 |
$=text |
以 text 结束的字符串 |
$!text |
不以 text 结束的字符串 |
或者完整表达式写法:
Expression | Description |
---|---|
:equal:text |
内容为 text 的字符串 |
:not-equal:text |
内容不等于 text 的字符串 |
:equal-i:text |
内容为 text 的字符串(忽略大小写) |
:not-equal-i:text |
内容不等于 text 的字符串(忽略大小写) |
:match:/abc/i |
内容匹配正则表达式 /abc/i 的字符串 |
:not-match:/abc/i |
内容不匹配正则表达式 /abc/i 的字符串 |
:include:text |
内容包含 text 的字符串 |
:not-include:text |
内容不包含 text 的字符串 |
:include-i:text |
内容包含 text 的字符串(忽略大小写) |
:not-include-i:text |
内容不包含 text 的字符串(忽略大小写) |
:start-with:text |
以 text 开头的字符串 |
:not-start-with:text |
不包以 text 开头的字符串 |
:start-with-i:text |
以 text 开头的字符串(忽略大小写) |
:not-start-with-i:text |
不以 text 开头的字符串(忽略大小写) |
:end-with:text |
以 text 结束的字符串 |
:not-end-with:text |
不包以 text 结束的字符串 |
:end-with-i:text |
以 text 结束的字符串(忽略大小写) |
:not-end-with-i:text |
不以 text 结束的字符串(忽略大小写) |
使用字面量表达式,可以校验数据是否为具体的值:
只支持
null
,number
,boolean
类型的字面量。
tgc.compile({ "rule": 123 }) // 被校验的变量必须为数值 123(类型上完全匹配)
tgc.compile({ "rule": null }) // 被校验的变量必须为 null (类型上完全匹配)
tgc.compile({ "rule": true }) // 被校验的变量必须为布尔值 true (类型上完全匹配)
tgc.compile({ "rule": false }) // 被校验的变量必须为布尔值 false (类型上完全匹配)
不支持字符串字面量,因为字符串表示各种复杂的规则,如果非要用,可以写作:
tgc.compile({ "rule": "==text" }) // 使用 `==` 字符串校验器
TypeGuard 支持对一个对象的属性极其类型进行校验,例如:
{
"name": "string",
"age": "uint8",
"father": {
"name": "string",
"age": "uint8"
}
}
可以匹配下面的数据:
{
"name": "Angus",
"age": 24,
"father": {
"name": "Elvis",
"age": 46
}
}
和
{
"name": "Edith",
"age": 24,
"gender": "female",
"father": {
"name": "Mark",
"age": 48
}
}
可以看出,这种校验仅仅判断这是否一个对象,且包含属性 name
和 age
,并校验其类型, 但不排斥其它多余的属性。这是对象校验的宽松模式。这种类型称为“对象”。
如果其中有可以省略的属性,则可以在属性名称后面加一个问号 ?,表示这个属性是可选的:
{
"name": "string",
"age?": "uint8"
}
既可以匹配
{"name":"Angus","age":24}
也可以匹配
{"name":"Angus"}
因为其中的 age 属性是可选的。但是不能匹配
{"name":"Angus","age":"24"}
因为 age 被指定为 uint8 类型(或 void),不能是别的类型。事实上上面的类型 也可以写成:
{
"name": "string",
"age": ["void", "uint8"]
}
又或者:
{
"name": "string",
"age": "?uint8"
}
?uint8
这种省略表达式参考 4.1. 复合类型(Mixed-Type)
三种写法效果一致。
如果要表达一个对象的其中一个属性是个数组,例如:
{
"friends": "string[]"
}
可以写作
{
"friends->[]": "string"
}
当然这看起来没什么用,可能还不如前者直观,但如果数组的元素类型是个对象,那就会方便很多了:
{
"friends->[]": {
"name": "string",
"age": "uint8"
}
}
还可以支持定长数组:
{
"friends->[5]": {
"name": "string",
"age": "uint8"
}
}
{
"friends->[3,5]": {
"name": "string",
"age": "uint8"
}
}
同理也支持映射:
{
"friends->{}": {
"name": "string",
"age": "uint8"
}
}
对于下面的写法:
{
"a": ["$.strict", {
"type": "string",
"value": "any"
}]
}
可以简化为:
{
"a->()": {
"type": "string",
"value": "any"
}
}
对于下面的写法:
{
"a": ["$.equal", {
"type": "string",
"value": "any"
}]
}
可以简化为:
{
"a->(=)": {
"type": "string",
"value": "any"
}
}
如果有一种对象,它是有几个固定属性的映射。
比如:
{
"total": 123,
"ccc": "fsdfasfdas",
"aaa": "ccasdsada"
}
规则可以写作如下:
{
"total": "uint32",
"$.map": "string"
}
高级类型有很多种,它们的特征都是用一个 JSON 数组包裹,第一个元素是一个以 $.
开头的字符串。第一个元素是这个类型的修饰符,表示这个类型的作用。
修饰符有很多种,下面会一一介绍。
如果第一个元素,即修饰符被省略,则默认为
$.or
修饰符。
如果想表达更加复杂的类型,比如既可以是字符串,也可以是整数,则可以写成:
["$.or", "string", "int"]
这是一个 JSON 数组,但是并不表示这是一个数组类型,而是表示这是一个复合类型。
可以看出 $.or
表示后面的类型可以组合使用,组合关系为“或”。
利用复合类型,可以实现枚举类型。例如字符串枚举:
["$.or", "=a", "=b", "=c"]或者整数枚举(字面量匹配)
[2, 4, 8, 16]此处
$.or
可以省略,因为对于高级类型,在省略修饰符的时候,默认是复合类型。利用复合类型,可以实现可选类型。例如一个可选的字符串类型:(下面三种写法等价)
["$.or", "void", "string"] ["$.or", "optional", "string"] ["$.or", "undefined", "string"]如果与
void
、optional
、undefined
混用的是一个基本类型,例如int
,那么可以简写 为?int
,还可以使用增强校验和数组、映射表达式:
?int(-123, 123)
?int{}
?int[]
?int(-123, 123)[32]
?int(-123, 123){}
甚至可以写 "?void",表示可选的 void 类型,虽然没有任何意义。
如何判断一个变量是一个值在 15 ~ 25 之间的整数?可以使用 $.and
修饰符 构造一个组合类型:
["$.and", "int", "|value between 15 25"]
表示被校验的值必须是一个整数,且取值范围在 15 和 25 之间(包括)。
当然现在也可以用
|int between 15 25
或者int(15, 25)
来表示了。
对于下列规则:
{
"quantity": "uint8",
"product_id": "uint32"
}
验证以下内容时没有问题:
{
"quantity": 7,
"product_id": 212931931
}
但是如果这样的,那就不行了:
{
"quantity": "7",
"product_id": "212931931"
}
当然,并不是说规则校验器不合理,它确实做了它应该做的事。但很多时候,我们进行数据校验的时候,
会遇到数据都是字符串的情况(例如来自 QueryString 或者 x-www-form-urlencoded
编码的
数据,那么就几乎无法使用迄今为止我们介绍过的 TypeGuard 语法进行精确校验了。
为此,我们需要使用一个叫 $.string
的修饰符,它表示,它修饰的规则,所要校验的数据(可能)是
以字符串形式表述的。以上面的规则威力,改写为:
["$.string", {
"quantity": "uint8",
"product_id": "uint32"
}]
再对前面的两份数据进行校验,成功!
对于基本类型,可以使用这样的语法来表示对应的数组:
string[]
那如果是个对象呢?如果是个高级类型呢?比如:
{
"name": "string",
"age": "uint8"
}
这时候就可以用 $.list
修饰符实现:
["$.list", {
"name": "string",
"age": "uint8"
}]
所以, string[]
也等价于
["$.list", "string"]
复合类型的数组:
["$.list", ["string", "uint"]]
上一节介绍了 $.list
修饰符,如果是定长数组该怎么写?用基本校验器?
[
"$.and",
["$.list", {
"name": "string",
"age": "uint8"
}],
"|array.length eq 5"
]
太麻烦了,可以使用 $.array
简化为:
[
"$.array",
5,
{
"name": "string",
"age": "uint8"
}
]
或者变长数组(长度为 2 ~ 32)
[
"$.array",
[2, 32],
{
"name": "string",
"age": "uint8"
}
]
和数组类型相似,复杂结构的映射可以写作如下:
对象类型的映射:
["$.map", {
"name": "string",
"age": "uint8"
}]
复合类型的映射:
["$.map", ["string", "uint"]]
字典和映射相似,但是字典是给定 key 列表的。
对象类型的映射:
对于
key
有且只能由Mick
、Mick
、Jack
三个取值。
["$.dict", ["Mick", "Sarah", "Jack"], {
"name": "string",
"age": "uint8"
}]
可以匹配
{
"Mick": {
"name": "Mick",
"age": 32
},
"Sarah": {
"name": "Sarah",
"age": 21
},
"Jack": {
"name": "Jack",
"age": 19
}
}
如果改成如下:
["$.dict", ["Mick", "Sarah", "Jack"], ["void", {
"name": "string",
"age": "uint8"
}]]
则可以匹配
{
"Mick": {
"name": "Mick",
"age": 32
}
}
但是不能拿匹配
{
"Lily": {
"name": "Lily",
"age": 23
}
}
因为,Key Lily
不在许可范围内。
元组是一种特殊的数组,它可以规定数组的每一个元素的类型。元组使用 $.tuple 修饰符构造
["$.tuple", "string", "uint8"]
表示这是一个有且只有两个元素的数组,第一个元素为字符串,第二个元素为8位整数,可以匹配
["a", 123]
但是不能匹配
["a", "123"]
如果有连续多个相同类型,可以简写为
["$.tuple", "int", "uint32", "...32", "string"]
表示第一个元素是 int
,第 2 ~ 33 个元素是 uint32
,最后一个元素是 string
。
以上元组是固定长度的数组。如果只想规定数组的前几个元素,那么可以用
["$.tuple", "int", "string", "uint32", "..."]
这是个无限长度的元组。
那么,除了第一个和第二个元素分别是 int
和 string
类型,剩下的元素都是 uint32
类型。
以下情况会报错
["$.tuple", "..."] // 未知类型
甚至可以这么写,
["$.tuple", "int", "uint32", "...32", "string", "..."]
表示第一个元素是 int
,第 2 ~ 33 个元素是 uint32
,剩下的所有元素都是 string
。
前面提到,对象校验只是检查规则里描述的每个属性是否合法,其他多余的属性是忽略掉,不检查的。
而如果要禁止多余的属性,则可以使用 $.strict
修饰符:
["$.strict", {
"name": "string",
"age": "uint8",
"friends->[]": {
"name": "string",
"age": "uint8"
}
}]
则只能匹配如:
{
"name": "Mick",
"age": 32,
"friends": []
}
而无法匹配
{
"name": "Mick",
"age": 32,
"gender": "Male",
"friends": []
}
因为 gender
属性不在规则列表内。
这里需要注意一点,$.strict
不对属性子对象生效,比如上面的规则,仍然可以校验下面的结构:
{
"name": "Mick",
"age": 32,
"friends": [{
"name": "Sarah",
"age": 21,
"gender": "Female"
}]
}
$.equal
的作用和 $.strict
完全一样,除了一点:它对属性子对象也生效。
如果有一个稍微有点复杂的类型,在一个结构内到处引用,那么可以将之定义为“预定义类型”。
例如:
tgc.compile({
"rule": {
"a": ["$.type", "MyType", "string[32]"],
"b": "@MyType",
"c": "@MyType"
}
});
这段代码注册了一个名为 MyType
的预定义类型。然后通过 @ 符号加预定义类型名称引用这个类型。
预定义类型有几个注意事项:
-
定义后可以在多次编译过程中反复使用:
tgc.compile({ "rule": ["$.type", "MyType", "string[32]"] }) tgc.compile({ "rule": { "a": "@MyType", "b": "@MyType", "c": "@MyType" } });
-
类型名称不能为空字符串,只能包含字母、数字、下划线、冒号、横线、小数点,且严格区分大小写。
-
同一个类型名称不得重复定义。
除此之外,可以使用 IInlineCompiler.addPredefinedType
方法在 JavaScript 中注册预定义类型处理器回调函数。
这种情况下,在引用该类型时,可以传递变量以定制化类型:
tgc.addPredefinedType("trim_string", (value: unknown, minLen: number = 0, maxLen?: number) => {
return typeof value === "string" && value.trim().length >= minLen && value.trim().length <= (maxLen ?? value.length);
});
然后在规则中引用它:
tgc.compile({
rule: '@trim_string(1, 32)'
});
即可定制出一个 1 ~ 32
个字符的字符串校验器,且忽略掉首尾空格。
还须注意的是,参数值只能是基础数据类型:
- 字符串(单引号或双引号,支持 JSON 内的字符串编码)
- 数字(整数或浮点数,支持十六进制,也可以是负数,但不能使用
+
号显式声明正数) - 布尔值
true
和false
null
不支持尾逗号(Trailing comma)。
使用 $.not
修饰可以对其修饰的规则进行否定断言,比如
["$.not", "string", "boolean"]
相当于
["$.not", ["$.or", "string", "boolean"]]
也就是既不是字符串,也不是布尔值。
对于基本类型,或者字符串校验器、数值校验器,可以简写如下:
!string // 不是字符串
!uint32 // 不是UInt32
!|value between 1 21 // 大于 21 或者小于 1
!~=/hello/i // 等价于 ~!/hello/i
使用 $.enum
修饰可以方便地对其修饰的规则进行枚举值断言,比如:
如果要对 "aaa" | "bbb" | "ccc"
类型进行断言检测,那么此前版本中会写作:
["==aaa", "==bbb", "==ccc"]
即:
["$.or", "==aaa", "==bbb", "==ccc"]
这样写实在是有点麻烦,在 v1.4.0 版本开始,可以简写为:
["$.enum", "aaa", "bbb", "ccc"]
与上面使用 ==
运算符的效果完全一样。
注意:
-
枚举值是字面量,里面的字符串里的特殊表达式也将被当作普通字符串处理,比如
["$.enum", "==aaa"]
匹配的值是==aaa
而不是aaa
。 -
枚举值列表不能为空,否则会报错。
-
枚举值列表的元素类型不要求一致,可以多种类型组合,但元素值必须是以下类型之一:
- string
- number
- boolean
- null