Why

社区中有很多数据校验库,各功能不一。有些库有你想要的功能,有些库又没有。有写库又喜欢定义自己的模板,然后这些模板改着改着就成了 Magic String(魔符)

例如这样的模板:

validate(data, 'size:4')
validate(data, 'size:2-4')
validate(data, 'lt:20 || gt: 60 && is_prime')
...

现在是大前端时代,讲究的是 All in Javascript。也符合 React 的哲学。另一个套路 Javascript in HTML(Template) 是不友好的(AngularJs 我用了一年多)

目前比较满意的应该是这一款: superstruct

但是对我而言,它还是不顺手,如何校验多个条件? 如何做无限嵌套,包括数组/对象嵌套。

先上一个结构体,你们感受一下

深度嵌套的对象

// 如何检验这个数据, 虽然实际中,谁这么定义数据结构,会被打死
const data1 = {
  name: "axetroy",
  age: 18,
  country: {
    name: "China",
    province: {
      name: "You Guess",
      city: {
        name: "The capital of the province",
        street: {
          name: "suburbs",
          number: {
            code: 100031,
            owner: "Axetroy",
            family: [
              { name: "daddy" },
              { name: "mom" },
              { name: "brother" },
              { name: "sister" }
            ]
          }
        }
      }
    }
  }
};

所以才写一个自己顺手的东西。

结构体

先来一个简单的类型校验,只有名字和年龄

const { Struct, type } = require("@axetroy/struct");

const struct = Struct({
  name: type.string,
  age: type.int
});

它还可以这么写, 自己自定义一个 func 作为校验

const struct = Struct({
  name: type.string,
  age: type.func(function(input) {
    return parseInt(input) === input;
  })
});

甚至几个条件去校验一个字段, 带着参数

const struct = Struct({
  name: type.string,
  age: type.int.gte(18).lte(50) // 需要 >=18 && <=50
});

然后你还能定义某个字段的错误信息

const struct = Struct({
  name: type.string,
  age: type.int.gte(18).lte(50).msg("保险仅限于18-50周岁之间")
});

然后还支持数组

const struct = Struct({
  name: type.string,
  age: type.int,
  friends: [type.string]
});

同样支持数组里面嵌套对象

const struct = Struct({
  name: type.string,
  age: type.int,
  friends: [
    {
      name: type.string,
      age: type.int
    }
  ]
});

还可以自定义函数

const struct = Struct({
  name: type.string,
  age: type.int,
  friends: [
    function(element) {
      if (typeof element.name !== "string") {
        return false;
      } else if (typeof element.age !== "number") {
        return false;
      }
      return true;
    }
  ]
});

如果你还想组合多个校验器,还可以这么用

const struct = Struct({
  name: type.string,
  age: type.int,
  friends: [
    type.func(function(element) {
      if (typeof element.name !== "string") {
        return false;
      } else if (typeof element.age !== "number") {
        return false;
      }
      return true;
    }).isAdult
  ]
});

内置的校验器基本够平时校验使用

  • number
  • int
  • float
  • string
  • bool
  • any
  • odd
  • even
  • json
  • object(object)
  • array(type)
  • eq(value)
  • gt(number)
  • gte(number)
  • lt(number)
  • lte(number)
  • bt(minNumber, maxNumber)
  • in(array)
  • len(int)
  • msg(message)
  • func(validatorFunc)

如果这些都没有你想要的,那么你还可以自定义

Struct.define("email", function(input) {
  return /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/.test(
    input
  );
});

// 然后这么使用
const struct = Struct({
  name: type.string,
  age: type.int,
  email: type.email
});

如果想定义一个带参数的校验器

// 发现了吗,校验器名字,跟上面的区别就是多了括号()
Struct.define("prefixWith()", function(prefix) {
  return function(input) {
    return input.indexOf(prefix) === 0;
  };
});

const struct = Struct({
  name: type.string.prefixWith("[A]"), // 名字必须是字符串,并且以[A]开头
  age: type.int
});

好了,最后回归到我们开头说的,深度嵌套的对象,怎么去解析

const struct = Struct({
  name: type.string,
  age: type.int,
  country: {
    name: type.string,
    province: {
      name: type.string,
      city: {
        name: type.string,
        street: {
          name: type.string,
          number: {
            code: type.int,
            owner: type.string,
            family: [
              {
                name: type.string
              }
            ]
          }
        }
      }
    }
  }
});

但是实际当中,我们可能不会这么定义这么一个庞大的数据结构。

而是分成细化的结构体。

比如一篇文章:

const User = Struct({
  name: type.string,
  age: type.int
});

const Article = Struct({
  title: type.string,
  content: type.string,
  author: User // 作者嵌套ser
});

在比如一个项目的信息:

const User = Struct({
  name: type.string,
  age: type.int
});

const Project = Struct({
  name: type.string,
  contributors: [User] // 数组的元素为User结构体
});

前面都说了定义结构体,那么输出呢? 依旧从最简单的入手

const struct = Struct({
  name: type.string,
  age: type.int
});

const err = struct.validate(data);

console.log(err);

struct.validate是执行校验工作。

如果校验过程中,有某个字段,不符合校验器的要求。

那么校验终止,并且返回这个错误。

如果校验成功,那么返回undefined

校验失败返回的错误为自定义错误TypeError,继承自Error

TypeError有一下属性:

  • validator: 失败的校验器名字
  • path: 路径数组。
  • value: 校验失败的值
  • message: 错误信息, 默认与 detail 同值,除非你使用.msg(errorMessage)去修改错误信息
  • detail: 默认的错误信息
{ Error
    at Object. (/home/axetroy/gpm/github.com/axetroy/struct/src/error.js:19:23)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Module.require (module.js:579:17)
    at require (internal/module.js:11:18)
    at Object. (/home/axetroy/gpm/github.com/axetroy/struct/src/type.js:2:19)
    at Module._compile (module.js:635:30)
  validator: 'int',
  path: [ 'author', 'age' ],
  value: '18',
  detail: 'Can not pass the validator "int" with value "18" in path "age"',
  message: 'Can not pass the validator "int" with value "18" in path "age"' }

最后

总结一下这个库的特点:

  • 全部是 Javascript 语法, 没有自定义模板字符串.
  • 严格模式. 没有定义类型的字段会不通过
  • 支持结构体嵌套
  • 可扩展, 可以在行内写校验器, 也可以新增一个全局类型.
  • 校验器支持带参数.
  • 简明的错误信息输出
  • 支持无线嵌套对象/数组. 数组里面嵌套对象, 然后对象里面又嵌套数组. OK.

数据校验库层出不穷,关键也不看是不是很强大。

关键是要自己用得顺手。

代码覆盖率 100%,大量测试通过。

目前已运用到实际的生产环境中。

应用在哪里?

  • 配置文件, 程序运行时读取配置文件进行校验
  • 测试接口数据的输入/出校验

库没有使用 es6 语法, 也没有使用 Babel 编译, 没有使用 Webpack 打包.

但是理论上是支持浏览器和 Node 的.

浏览器需要使用打包工具引入.

最后上项目地址: struct