A better struct validation in Golang
Validation is needed to make sure that the content of a struct or a type is in the format you require. In the case of user registration, you may want to check that email is valid and password is strong. Validation is useful when unmarshalling form data, YAML, or JSON into a struct.
There are multiple methods to validate structs and types in Golang. One of the most promising methods is validation using tags and reflection.
The most popular implementation of such method is go-playground/validator package. However, it is not without limitations. In this article, I am going to present my new Go package to perform a better struct/type validation using tags and reflection, and explain why it is better.
Meet dealancer/validate package!
Nicer syntax
Let’s say we have a complex field in a struct.
type S struct {
Complex []map[string]int
}
To validate it, dealancer/validate package provides easy to read and implement syntax.
`validate:"empty=false > empty=false [empty=false] > ne=0"`
The same functionality in go-playground/validator can be achieved with a following syntax.
`validate:"required,dive,required,dive,keys,required,endkeys,dive,ne=0"`
Explicit pointer validation
Let’s say we have a pointer field in a struct.
type S struct {
Field *string
}
To validate it, dealancer/validate package provides a straightforward way.
`validate:"nil=false > empty=false"`
There is no unambiguous way to validate the same struct in go-playground/validator. Following won’t work because required is applied to the pointer two times.
`validate:"required,required"`
But, there is a workaround, which looks strange.
`validate:"gt=0,required"`
Familiar logical operators precedence
In dealancer/validate package, logical AND &
is more prior to logical OR |
, so you can easily validate a string field with a rule like this: accept empty strings or ones with length between 5 and 10 characters.
`validate:empty=true | gte=5 & lte=10`
In go-playground/validator package this can be achieved using omitempty
validator, which is not very intuitive.
omitempty,gte=5,lte=10
Refined validator names
In dealancer/validate package, format=number
validator checks that a string is a number so -1.1
validates correctly. However in go-playground/validator package, number
is something different: it checks that all string’s characters are digits, so -1.1
validation fails.
Also in dealancer/validate package there are no amigos or aliased validators. I tried to keep a number of validators to the minimum.
Validation of an array/slice/map of structs
dealancer/validate package provides Validate
method that works recursively over any data structure. For example, you can run validation over an array of structs and it will work.
err := validate.Validate([3]S{s1, s2, s3});`
I don’t think it is possible to do using go-playground/validator package.
Custom validation
dealancer/validate package allows you to defined Validate
method for a type to perform a custom validation.
// Custom validation
func (s S) Validate() error {
if !StrongPassword(s.Field) {
return errors.New("Password should be strong!")
} return nil
}
Things TODO
dealancer/validate is quite a new project, though it is already covered with 100% of tests, the current version is v2, and I am planning to work on v3 in the nearest future. This newer version will provide:
()
parentheses to allow to group logical AND/OR operators- Better error handling (e.g. customizable text, localization, web forms support, etc.)
- Escaped characters support to allow entering reserved characters
Give it a try!
go get gopkg.in/dealancer/validate.v2