A better struct validation in Golang

Ovadiah Myrgorod
3 min readJun 5, 2019

--

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

--

--

Ovadiah Myrgorod

Welcome to the day that makes it a good day everyday!