golang JSON Schema验证插件库jiojoi的使用
Golang JSON Schema验证插件库jio的使用
jio是一个简单高效的JSON Schema验证库,可以帮助你在Golang中进行JSON数据验证。
为什么使用jio?
在Golang中进行参数验证是一个常见问题。在结构体上定义标签不容易扩展规则,手写验证代码会使逻辑代码变得冗长,结构体字段的初始零值也会干扰验证。
jio尝试在反序列化之前验证原始JSON数据来避免这些问题。将验证规则定义为Schema易于阅读和扩展(灵感来自Hapi.js的joi库)。
如何使用?
验证JSON字符串
package main
import (
"log"
"github.com/faceair/jio"
)
func main() {
data := []byte(`{
"debug": "on",
"window": {
"title": "Sample Widget",
"size": [500, 500]
}
}`)
_, err := jio.ValidateJSON(&data, jio.Object().Keys(jio.K{
"debug": jio.Bool().Truthy("on").Required(),
"window": jio.Object().Keys(jio.K{
"title": jio.String().Min(3).Max(18),
"size": jio.Array().Items(jio.Number().Integer()).Length(2).Required(),
}).Without("name", "title").Required(),
}))
if err != nil {
panic(err)
}
log.Printf("%s", data) // {"debug":true,"window":{"size":[500,500],"title":"Sample Widget"}}
}
上面的schema定义了以下约束:
debug
- 不能为空,验证结束时必须是布尔值
- 允许使用"on"字符串代替true
window
- 不能为空,必须是对象
- 不允许同时存在"name"和"title"
- 包含以下元素:
title
- 字符串,可以为空
- 不为空时长度在3到18之间
size
- 数组,不能为空
- 包含两个整数类型的子元素
使用中间件验证请求体
以chi为例,其他框架类似:
package main
import (
"io/ioutil"
"net/http"
"github.com/faceair/jio"
"github.com/go-chi/chi"
)
func main() {
r := chi.NewRouter()
r.Route("/people", func(r chi.Router) {
r.With(jio.ValidateBody(jio.Object().Keys(jio.K{
"name": jio.String().Min(3).Max(10).Required(),
"age": jio.Number().Integer().Min(0).Max(100).Required(),
"phone": jio.String().Regex(`^1[34578]\d{9}$`).Required(),
}), jio.DefaultErrorHandler)).Post("/", func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write(body)
})
})
http.ListenAndServe(":8080", r)
}
jio.ValidateBody
的第二个参数在验证失败时被调用用于错误处理。
使用中间件验证查询参数
package main
import (
"encoding/json"
"net/http"
"github.com/faceair/jio"
"github.com/go-chi/chi"
)
func main() {
r := chi.NewRouter()
r.Route("/people", func(r chi.Router) {
r.With(jio.ValidateQuery(jio.Object().Keys(jio.K{
"keyword": jio.String(),
"is_adult": jio.Bool().Truthy("true", "yes").Falsy("false", "no"),
"starts_with": jio.Number().ParseString().Integer(),
}), jio.DefaultErrorHandler)).Get("/", func(w http.ResponseWriter, r *http.Request) {
query := r.Context().Value(jio.ContextKeyQuery).(map[string]interface{})
body, err := json.Marshal(query)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write(body)
})
})
http.ListenAndServe(":8080", r)
}
注意查询参数的原始值是字符串,你可能需要先转换值类型(例如jio.Number().ParseString()
或jio.Bool().Truthy(values)
)。
高级用法
工作流程
每个Schema由一系列规则组成,例如:
jio.String().Min(5).Max(10).Alphanum().Lowercase()
在这个例子中,String Schema有4个规则,分别是Min(5)
Max(10)
Alphanum()
Lowercase()
,将按顺序验证Min(5)
Max(10)
Alphanum()
Lowercase()
。如果一个规则验证失败,Schema的验证将停止并抛出错误。
为了提高代码可读性,这三个内置规则会先验证:
Required()
Optional()
Default(value)
例如:
jio.String().Min(5).Max(10).Alphanum().Lowercase().Required()
实际的验证顺序将是Required()
Min(5)
Max(10)
Alphanum()
Lowercase()
。
验证完所有规则后,最后我们检查数据的基本类型是否是Schema的类型。如果不是,Schema将抛出错误。
验证器上下文
工作流中的数据传递依赖于上下文,结构如下:
type Context struct {
Value interface{} // 原始数据,你也可以重新赋值来改变结果
}
func (ctx *Context) Ref(refPath string) (value interface{}, ok bool) { // 引用其他字段数据
}
func (ctx *Context) Abort(err error) { // 终止验证并抛出错误
...
}
func (ctx *Context) Skip() { // 跳过后续规则
...
}
让我们尝试自定义一个验证规则。使用Transform
方法添加规则:
jio.String().Transform(func(ctx *jio.Context) {
If ctx.Value != "faceair" {
ctx.Abort(errors.New("you are not faceair"))
}
})
我们添加的自定义规则意味着当原始数据不等于"faceair"时抛出"you are not faceair"错误。
事实上,内置验证规则的工作方式类似。例如,Optional()
的核心代码是:
If ctx.Value == nil {
ctx.Skip()
}
你也可以重新赋值ctx.Value
来改变原始数据。例如,内置的Lowercase()
将原始字符串转换为小写。核心代码是:
ctx.Value = strings.ToLower(ctx.Value)
引用和优先级
在大多数情况下,规则只使用当前字段的数据,但有时需要与其他字段配合工作。例如:
{
"type": "ip", // 枚举值,`ip`或`domain`
"value": "8.8.8.8"
}
这个value
的验证规则由type
的值决定,可以写成:
jio.Object().Keys(jio.K{
"type": jio.String().Valid("ip", "domain").SetPriority(1).Default("ip"),
"value": jio.String().
When("type", "ip", jio.String().Regex(`^\d+\.\d+\.\d+\.\d+$`)).
When("type", "domain", jio.String().Regex(`^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0 -9]\.[a-zA-Z]{2,}$`)).Required(),
})
When
函数可以引用其他字段数据,如果成功,则将新的验证规则应用于当前数据。
此外,你可能注意到type
的规则中有一个SetPriority
方法。如果输入数据是:
{
"value": "8.8.8.8"
}
当未设置优先级时,value
的验证规则可能会先执行。此时引用的type
值将为null,验证将失败。
因为有相互引用的验证规则,可能存在验证顺序要求。当我们希望同一个Object下的某个字段先验证时,可以将其设置为较大的优先级值(默认值0)。
如果你想在自定义规则中引用其他字段的数据,可以使用上下文上的Ref
方法。如果引用的数据是嵌套对象,引用字段的路径需要用.
连接。例如,如果你想引用people
对象下的name
,那么引用路径是people.name
:
{
"type": "people",
"people": {
"name": "faceair"
}
}
更多关于golang JSON Schema验证插件库jiojoi的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang JSON Schema验证插件库jiojoi的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
jiojoi - Golang JSON Schema 验证库
jiojoi 是一个轻量级的 Golang JSON Schema 验证库,它提供了简单易用的 API 来验证 JSON 数据是否符合预定义的 schema 规范。
安装
go get github.com/faceair/jiojoi
基本用法
1. 定义 Schema
首先需要定义一个 JSON Schema 来描述你的数据结构:
package main
import (
"fmt"
"github.com/faceair/jiojoi"
)
func main() {
schema := `{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 3,
"maxLength": 20
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"email": {
"type": "string",
"format": "email"
}
},
"required": ["name", "age"]
}`
2. 验证 JSON 数据
// 要验证的 JSON 数据
data := `{
"name": "John Doe",
"age": 30,
"email": "john@example.com"
}`
// 创建验证器
validator, err := jiojoi.NewValidatorFromString(schema)
if err != nil {
fmt.Println("Schema 解析错误:", err)
return
}
// 验证数据
result, err := validator.ValidateBytes([]byte(data))
if err != nil {
fmt.Println("验证错误:", err)
return
}
if result.Valid() {
fmt.Println("数据验证通过!")
} else {
fmt.Println("数据验证失败:")
for _, err := range result.Errors() {
fmt.Printf("- %s\n", err)
}
}
}
高级功能
自定义验证规则
// 自定义验证函数
func validateEvenNumber(value interface{}) error {
num, ok := value.(float64)
if !ok {
return fmt.Errorf("必须是数字")
}
if int(num)%2 != 0 {
return fmt.Errorf("必须是偶数")
}
return nil
}
func main() {
schema := `{
"type": "object",
"properties": {
"even_number": {
"type": "number",
"custom": "even"
}
}
}`
validator, err := jiojoi.NewValidatorFromString(schema)
if err != nil {
panic(err)
}
// 注册自定义验证器
validator.RegisterCustomValidator("even", validateEvenNumber)
data := `{"even_number": 3}`
result, _ := validator.ValidateBytes([]byte(data))
if !result.Valid() {
fmt.Println(result.Errors()[0]) // 输出: even_number: 必须是偶数
}
}
条件验证
schema := `{
"type": "object",
"properties": {
"country": {
"type": "string",
"enum": ["US", "CA", "UK"]
},
"zip_code": {
"type": "string"
}
},
"if": {
"properties": {
"country": { "const": "US" }
}
},
"then": {
"properties": {
"zip_code": {
"pattern": "^[0-9]{5}(-[0-9]{4})?$"
}
}
}
}`
性能优化技巧
-
重用验证器实例:验证器实例可以安全地并发使用,应该重用而不是每次验证都创建新实例
-
预编译 Schema:对于频繁使用的 Schema,可以预编译并缓存验证器
var userValidator *jiojoi.Validator
func init() {
var err error
userValidator, err = jiojoi.NewValidatorFromFile("user_schema.json")
if err != nil {
panic(err)
}
}
func ValidateUser(data []byte) error {
result, err := userValidator.ValidateBytes(data)
if err != nil {
return err
}
if !result.Valid() {
return fmt.Errorf("invalid user data: %v", result.Errors())
}
return nil
}
与其他库的比较
- gojsonschema:功能更全面但更重量级
- santhosh-tekuri/jsonschema:性能较好但 API 较复杂
- jiojoi:轻量级,API 简单,适合大多数基本验证需求
注意事项
- 确保 Schema 是有效的 JSON
- 错误信息可能不够详细,需要根据业务需求进一步处理
- 对于复杂的数据结构,考虑将 Schema 拆分为多个部分
jiojoi 是一个简单实用的 JSON Schema 验证工具,适合大多数 Golang 项目中的 JSON 数据验证需求。