golang运行时值解析与验证的Zod风格模式构建插件Zog的使用

Golang运行时值解析与验证的Zod风格模式构建插件Zog的使用

Zog是一个用于运行时值解析和验证的Golang库,类似于Zod和Yup的Schema解析器和验证器。它提供了简洁而富有表现力的API,能够处理从简单到复杂的数据模型验证。

主要特性

  • 简洁而富有表现力的Schema接口
  • 类似Zod的API,使用方法链构建类型安全的Schema
  • 可扩展:可以添加自定义的测试和Schema
  • 丰富的错误信息,便于调试
  • 高性能:Zog是Go中最快的验证库之一
  • 内置大多数类型的强制转换支持
  • 零依赖
  • 提供四个辅助包:zenv、zhttp、zjson和i18n

安装

go get github.com/Oudwins/zog

完整示例

1. 创建用户Schema和结构体

import (
	z "github.com/Oudwins/zog"
)

type User struct {
	Name string
	Age  int
}

var userSchema = z.Struct(z.Shape{
	// 非常重要:Schema键(如"name")必须匹配结构体字段名,而不是输入数据
	"name": z.String().Min(3, z.Message("Override default message")).Max(10),
	"age":  z.Int().GT(18),
})

2. 使用schema.Parse()验证

func main() {
	u := User{}
	m := map[string]string{
		"name": "Zog",
		"age":  "", // 不会返回错误,因为字段默认是可选的
	}
	errsMap := userSchema.Parse(m, &u)
	if errsMap != nil {
		// 处理错误 -> 参见错误部分
	}
	u.Name // "Zog"
	// 注意:这看起来可能有点奇怪,但我们没有说age是必填的,
	// 所以Zog只是跳过了空字符串,我们得到了未初始化的int
	// 如果我们希望0是age的有效值,可以使用int指针,如果输入数据中没有值,指针将为nil
	u.Age // 0
}

3. 使用schema.Validate()验证

func main() {
	u := User{
		Name: "Zog",
		Age:  0, // 不会返回错误,因为字段默认是可选的否则会报错
	}
	errsMap := userSchema.Validate(&u)
	if errsMap != nil {
		// 处理错误 -> 参见错误部分
	}
}

4. 与HTTP和JSON一起使用

使用zhttp包处理JSON、表单和查询参数:

import (
	zhttp "github.com/Oudwins/zog/zhttp"
)

err := userSchema.Parse(zhttp.Request(r), &user)

使用zjson包处理JSON数据:

import (
	zjson "github.com/Oudwins/zog/zjson"
)

err := userSchema.Parse(zjson.Decode(bytes.NewReader(jsonBytes)), &user)

5. 验证环境变量

使用zenv包验证环境变量:

import (
	zenv "github.com/Oudwins/zog/zenv"
)

err := envSchema.Parse(zenv.NewDataProvider(), &envs)

6. 解析单个字段

var t = time.Time
errsList := Time().Required().Parse("2020-01-01T00:00:00Z", &t)

7. 无限制地转换数据

var dest []string
schema := z.Preprocess(func(data any, ctx z.Ctx) ([]string, error) {
	s := data.(string) // 不要这样做,实际上应该检查类型
	return strings.Split(s, ","), nil
}, z.Slice(z.String().Trim().Email().Required()))
errs := schema.Parse("foo@bar.com,bar@foo.com", &dest) // dest = [foo@bar.com bar@foo.com]

路线图

  • 支持schema.Clone()
  • 支持structs和slices的catch和default
  • 从Schema生成结构体

许可证

该项目采用MIT许可证 - 详情请参见LICENSE文件。


更多关于golang运行时值解析与验证的Zod风格模式构建插件Zog的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang运行时值解析与验证的Zod风格模式构建插件Zog的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang运行时值解析与验证的Zod风格插件:Zog

在JavaScript生态中,Zod是一个非常流行的运行时类型验证库。在Go语言中,我们可以构建类似的运行时值解析与验证工具。下面我将介绍如何实现一个类似Zod风格的Go插件"Zog"。

Zog核心设计

Zog的设计目标是提供一种声明式、链式调用的API来定义和验证数据结构,类似于Zod的风格:

package zog

type Schema[T any] interface {
    Parse(data interface{}) (T, error)
    // 其他验证方法...
}

基本类型验证

首先实现基础类型的验证器:

func String() *StringSchema {
    return &StringSchema{}
}

type StringSchema struct {
    checks []func(string) error
}

func (s *StringSchema) Parse(data interface{}) (string, error) {
    str, ok := data.(string)
    if !ok {
        return "", fmt.Errorf("expected string, got %T", data)
    }
    
    for _, check := range s.checks {
        if err := check(str); err != nil {
            return "", err
        }
    }
    
    return str, nil
}

func (s *StringSchema) Min(length int) *StringSchema {
    s.checks = append(s.checks, func(str string) error {
        if len(str) < length {
            return fmt.Errorf("string must be at least %d characters", length)
        }
        return nil
    })
    return s
}

func (s *StringSchema) Max(length int) *StringSchema {
    s.checks = append(s.checks, func(str string) error {
        if len(str) > length {
            return fmt.Errorf("string must be at most %d characters", length)
        }
        return nil
    })
    return s
}

对象结构验证

实现类似Zod的对象结构验证:

func Object[T any](shape map[string]Schema) *ObjectSchema[T] {
    return &ObjectSchema[T]{
        shape: shape,
    }
}

type ObjectSchema[T any] struct {
    shape map[string]Schema
}

func (s *ObjectSchema[T]) Parse(data interface{}) (T, error) {
    var result T
    m, ok := data.(map[string]interface{})
    if !ok {
        return result, fmt.Errorf("expected object, got %T", data)
    }
    
    // 使用反射构建结果
    v := reflect.ValueOf(&result).Elem()
    t := v.Type()
    
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        key := field.Tag.Get("json")
        if key == "" {
            key = field.Name
        }
        
        if schema, ok := s.shape[key]; ok {
            val, err := schema.Parse(m[key])
            if err != nil {
                return result, fmt.Errorf("field %s: %w", key, err)
            }
            
            fv := v.Field(i)
            if fv.CanSet() {
                fv.Set(reflect.ValueOf(val))
            }
        }
    }
    
    return result, nil
}

使用示例

下面是如何使用Zog进行验证的示例:

type User struct {
    Name     string `json:"name"`
    Age      int    `json:"age"`
    Email    string `json:"email"`
}

userSchema := zog.Object[User](map[string]zog.Schema{
    "name": zog.String().Min(3).Max(50),
    "age": zog.Number().Min(18).Max(120),
    "email": zog.String().Email(),
})

func HandleRequest(data []byte) error {
    var input map[string]interface{}
    if err := json.Unmarshal(data, &input); err != nil {
        return err
    }
    
    user, err := userSchema.Parse(input)
    if err != nil {
        return fmt.Errorf("validation error: %w", err)
    }
    
    // 使用验证后的user...
    return nil
}

高级特性实现

  1. 自定义验证器:
func (s *StringSchema) Custom(fn func(string) error) *StringSchema {
    s.checks = append(s.checks, fn)
    return s
}

// 使用示例
zog.String().Custom(func(s string) error {
    if s != strings.ToLower(s) {
        return errors.New("must be lowercase")
    }
    return nil
})
  1. 联合类型:
func Union(schemas ...Schema) *UnionSchema {
    return &UnionSchema{schemas: schemas}
}

type UnionSchema struct {
    schemas []Schema
}

func (s *UnionSchema) Parse(data interface{}) (interface{}, error) {
    var lastErr error
    
    for _, schema := range s.schemas {
        if val, err := schema.Parse(data); err == nil {
            return val, nil
        } else {
            lastErr = err
        }
    }
    
    return nil, fmt.Errorf("no matching schema found: %w", lastErr)
}

性能考虑

在Go中实现运行时验证需要注意性能:

  1. 使用缓存反射类型信息
  2. 避免不必要的内存分配
  3. 提供编译时验证选项(如代码生成)

总结

通过构建Zog这样的库,我们可以在Go中获得类似Zod的开发体验:

  • 声明式的模式定义
  • 链式API调用
  • 丰富的内置验证器
  • 类型安全的解析结果
  • 清晰的错误信息

虽然Go是静态类型语言,但这样的运行时验证在处理外部输入(如JSON、表单数据)时仍然非常有用,可以提供更好的错误处理和更健壮的系统边界。

完整的实现需要考虑更多细节,如嵌套对象、数组验证、可选字段等,但以上代码已经展示了核心概念。

回到顶部