⚔ Go package for data validation and filtering. support Map, Struct, Form data. Go通用的数据验证与过滤库,使用简单,内置大部分常用验证、过滤器,支持自定义验证器、自定义消息、字段翻译。
validate is a generic Go data validate and filter tool library.
Map, Struct, Request(Form, JSON, url.Values, UploadedFile) datahttp.Request automatically collects data based on the request Content-Type valuev.StringRule("tags.*", "required|string")message, label tags in structen, zh-CN, zh-TWvalidate in any frameworks, such as Gin, Echo, Chi and morevalidate.Val("[email protected]", "required|email")v2.0: the module path is now
github.com/gookit/validate/v2(requires Go 1.21+). See the upgrade guide for migrating from v1.x.Performance: see Benchmark comparison for the v1.5.7 → v1.6.0 → v2.0.0 benchmark results.
Install:
go get github.com/gookit/validate/v2Use the validate tag of the structure, you can quickly config a structure.
Field translations and error messages for structs can be quickly configured using the message and label tags.
json tag by defaultmessage taglabel tag…validate provides extended functionality:
The struct can implement three interfaces methods, which is convenient to do some customization:
ConfigValidation(v *Validation) will be called after the validator instance is createdMessages() map[string]string can customize the validator error messageTranslates() map[string]string can customize field translation…Can use validate.Struct(ptr) quick create a validation instance. then call v.Validate() for validating.
package main
import (
"fmt"
"github.com/gookit/validate/v2"
)
func main() {
u := &UserForm{
Name: "inhere",
}
v := validate.Struct(u)
// v := validate.New(u)
if v.Validate() { // validate ok
// do something ...
} else {
fmt.Println(v.Errors) // all error messages
fmt.Println(v.Errors.One()) // returns a random error message text
fmt.Println(v.Errors.OneError()) // returns a random error
fmt.Println(v.Errors.Field("Name")) // returns error messages of the field
}
}By default (CheckSubOnParentMarked=true), a named sub-struct field
(struct / *struct / slice-of-struct / map-of-struct) is descended into to
collect its inner rules only when it carries a validate tag. The tag
value may be empty — validate:"" is enough to mark the field. A named field
with no validate tag at all is not descended into. This is the Java
@Valid style of on-demand cascade, avoiding pointless recursion through the
whole struct tree.
Anonymous embedded structs are exempt: type Bar struct { Foo } (the field
is promoted and is part of the parent) always cascades, no tag needed. Only
named nested fields need the marker.
type Address struct {
City string `validate:"required"`
}
type User struct {
Name string `validate:"required"`
Home Address `validate:"required"` // has tag -> descend, validates Home.City
Work Address `validate:""` // empty tag is also a marker -> descend
Temp Address // no tag -> not descended, Temp.City ignored
}To restore v1's unconditional cascade behaviour globally:
validate.Config(func(o *validate.GlobalOption) {
o.CheckSubOnParentMarked = false
})See the upgrade guide for more on this behaviour change.
You can also validate a MAP data directly.
…Tip: for struct validation, prefer the top-level
validate.Check(&u)— it is stateless and pooled internally, and returns the same*ValidResult.
If it is an HTTP request, you can quickly validate the data and pass the verification. Then bind the secure data to the structure.
…Quick validate a struct (pooled internally, no manual lifecycle):
Check(structPtr any, scene ...string) *ValidResult — recommended default for struct validation. Stateless from the caller's side, internally pooled. Returns a *ValidResult carrying the cleaned/safe data and bind helpers.CheckErr(structPtr any, scene ...string) error — opt-in FAST entry for "only need ok/err, no safe data / no binding". Pooled like Check but it skips collecting safeData/filteredData and does not build a *ValidResult, so it reaches fewer allocations (struct-valid: 0 allocs vs Check 6, via RV-native end-to-end deboxing). Returns nil on pass, otherwise the first error.// fast pass/fail (e.g. middleware reject) — no cleaned data needed
if err := validate.CheckErr(&u); err != nil {
return err
}
// need the cleaned data or BindStruct? use Check / ValidateR instead
r := validate.Check(&u)
if r.Fail() {
return r.Err()
}
r.BindSafeData(&out)
CheckErris struct-only. For map data or programmatic rules, build aValidation(New/Map+StringRule...) and callValidateErr().
Quick create Validation instance.
New(data any, scene ...string) *ValidationRequest(r *http.Request) *ValidationJSON(s string, scene ...string) *ValidationStruct(s any, scene ...string) *ValidationMap(m map[string]any, scene ...string) *ValidationQuick create DataFace instance.
FromMap(m map[string]any) *MapDataFromStruct(s any) (*StructData, error)FromJSON(s string) (*MapData, error)FromJSONBytes(bs []byte) (*MapData, error)FromURLValues(values url.Values) *FormDataFromRequest(r *http.Request, maxMemoryLimit ...int64) (DataFace, error)Create
ValidationfromDataFace
d := FromMap(map[string]any{"key": "val"})
v := d.Validation()func (v *Validation) Validate(scene ...string) bool Do validating and return is success.func (v *Validation) ValidateE(scene ...string) Errors Do validating and return error.func (v *Validation) ValidateR(scene ...string) *ValidResult Validate and return a ValidResult decoupled from the instance.Since v2.0,
Validationkeeps only the pass/fail + error face. The safe-data / bind methods (SafeData()/Safe()/SafeVal()/Filtered()/FilteredData()/BindSafeData()/BindStruct()) moved to the result objectValidResult. Get one viavalidate.Check(&u)(recommended for structs, pooled) orv.ValidateR()(map/builder), then call e.g.r.SafeData()/r.BindStruct(ptr). For pass/fail only, usevalidate.CheckErr(structPtr any, scene ...string) error(fewest allocations).
v.Errors is map data, top key is field name, value is map[string]string.
// do validating
if v.Validate() {
return nil
}
// get errors
es := v.Errors
// check
es.Empty() // bool
// returns an random error, if no error returns nil
fmt.Println(v.Errors.OneError())
fmt.Println(v.Errors.ErrOrNil())
fmt.Println(v.Errors) // all error messages
fmt.Println(v.Errors.One()) // returns a random error message text
fmt.Println(v.Errors.Field("Name")) // returns error messages of the field Encode to JSON:
StopOnError=true(default), will only one error{
"field1": {
"required": "error msg0"
}
}StopOnError=false, will get multi error{
"field1": {
"minLen": "error msg1",
"required": "error msg0"
},
"field2": {
"min": "error msg2"
}
}You can adjust some processing logic of the validator by changing the global option settings.
…go validate.AddValidator("myCheck0", func(val any) bool { // do validate val ... return true }) validate.AddValidators(validate.M{ "myCheck1": func(val any) bool { // do validate val ... return true }, })
#### Add Temporary Validator
Again, you can add one or more custom validators at once.
```go
v := validate.Struct(u)
v.AddValidator("myFunc3", func(val any) bool {
// do validate val ...
return true
})
v.AddValidators(validate.M{
"myFunc4": func(val any) bool {
// do validate val ...
return true
},
})validate can also support adding custom filters, and supports adding global filter and temporary filter.
TIP: for filter func, we allow functions with 1 result or 2 results where the second is an error.
You can add one or more custom validators at once.
package main
import "github.com/gookit/validate/v2"
func init() {
validate.AddFilter("myToIntFilter0", func(val any) int {
// do filtering val ...
return 1
})
validate.AddFilters(validate.M{
"myToIntFilter1": func(val any) (int, error) {
// do filtering val ...
return 1, nil
},
})
}Again, you can add one or more custom filters at once.
package main
import "github.com/gookit/validate/v2"
func main() {
v := validate.New(&someStrcut{})
v.AddFilter("myToIntFilter0", func(val any) int {
// do filtering val ...
return 1
})
v.AddFilters(validate.M{
"myToIntFilter1": func(val any) (int, error) {
// do filtering val ...
return 1, nil
},
})
// use the added filter
v.FilterRule("field", "myToIntFilter0")
}required validationAllows a custom required validator to customize whether the validation is empty.
However, note that the validator name must start with required, e.g. required_custom.
type Data struct {
Age int `validate:"required_custom" message:"age is required"`
Name string `validate:"required"`
}
v := validate.New(&Data{
Name: "tom",
Age: 0,
})
v.AddValidator("required_custom", func(val any) bool {
// do check value
return false
})
ok := v.Validate()
assert.False(t, ok)
…
go
import (
"database/sql"
"reflect"
"github.com/gookit/validate/v2"
)
// extract the underlying string from sql.NullString; nil when invalid (= empty)
validate.AddCustomType(func(field reflect.Value) any {
ns := field.Interface().(sql.NullString)
if !ns.Valid {
return nil
}
return ns.String
}, sql.NullString{})
type Form struct {
Name sql.NullString `validate:"required|minLen:3"`
}
v := validate.Struct(&Form{Name: sql.NullString{Valid: true, String: "inhere"}})
_ = v.Validate() // true: extracted "inhere" then checked by required|minLen:3NewFactory)When you validate large batches of the same type, an opt-in Factory reuses
*Validation instances from a private pool to amortize the construction cost. The
default behaviour and lifecycle of Struct / Map / New are unchanged —
only callers who explicitly use a Factory get pooling.
Most callers should just use the top-level
validate.Check(&u), which pools instances via a package pool automatically. Reach for aFactory
No open issues yet, or sync has not completed.