Golang How to verify the data format of a struct field

Source: Internet
Author: User
Tags email string tagname valid email address
This is a creation in Article, where the information may have evolved or changed.

This article was also published in https://github.com/zhangyachen/zhangyachen.github.io/issues/125

Suppose we have the following structure:

type User struct {    Id    int        Name  string     Bio   string     Email string }

We need to verify the legitimacy of the fields in the structure:

    • The value of the ID is within a range.
    • The length of name is within a certain range.
    • The email format is correct.

We may be writing this:

user := User{        Id:    0,        Name:  "superlongstring",        Bio:   "",        Email: "foobar",}if user.Id < 1 && user.Id > 1000 {    return false}if len(user.Name) < 2 && len(user.Name) > 10 {    return false}if !validateEmail(user.Email) {    return false}

In this case, the code is redundant, and if the structure of the new field, you also need to modify the validation function and add an if judgment. This makes the code more redundant. We can use Golang's structtag to solve the above problems:

type User struct {    Id    int    `validate:"number,min=1,max=1000"`    Name  string `validate:"string,min=2,max=10"`    Bio   string `validate:"string"`    Email string `validate:"email"`}

validate:"number,min=1,max=1000"Is Structtag. If you are unfamiliar with this, look at the following:

type User struct {    Id        int       `json:"id"`    Name      string    `json:"name"`    Bio       string    `json:"about,omitempty"`    Active    bool      `json:"active"`    Admin     bool      `json:"-"`    CreatedAt time.Time `json:"created_at"`}

Golang has written about the basic use of json:xxx this usage, in fact, is json:xxx a structtag, but this is Golang help you achieve a specific use of structtag. And validate:"number,min=1,max=1000" It's our custom Structtag.

Implementation ideas

We define an interface Validator that defines a method Validate . Then define a specific validator such as, StringValidator NumberValidator EmailValidator to implement the interface Validator .
Why do we use interfaces here? Suppose we do not use interface code what will be written?

if tagIsOfNumber(){        validator := NumberValidator{}}else if tagIsOfString() {        validator := StringValidator{}}else if tagIsOfEmail() {        validator := EmailValidator{}}else if tagIsOfDefault() {        validator := DefaultValidator{}}

In this case, the judgment logic cannot be written in a function, because the return value validator is different because of the structtag, and validator cannot be passed as a function parameter. And we define an interface, all validator to implement this interface, the above problems can be solved, and the logic is more clear and compact.
On the use of interfaces can be seen in the standard library IO Writer,writer is a interface, there is only one method Writer:

type Writer interface {    Write(p []byte) (n int, err error)}

The output function can directly invoke the parameter's write method, regardless of whether it is written to the file or written to the standard output:

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {    p := newPrinter()    p.doPrintf(format, a)    n, err = w.Write(p.buf)      //调用Write方法    p.free()    return}//调用Fprintf(os.Stdout, format, a...)    //标准输出Fprintf(os.Stderr, msg+"\n", args...)   //标准错误输出var buf bytes.BufferFprintf(&buf, "[")    //写入到Buffer的缓存中

To get to the bottom, let's look at the complete code, which is given in the custom struct field tags in Golang:

Package Mainimport ("FMT" "Reflect" "regexp" "strings") const TAGNAME = "Validate"//mailbox Verification regular var mailre = RegExp. Mustcompile (' \a[\w+\-.] +@[a-z\d\-]+ (\.[ a-z]+) *\. [a-z]+\z ')//authentication interface type Validator interface {Validate (interface{}) (bool, error)}type defaultvalidator struct {}func (v D    Efaultvalidator) Validate (Val interface{}) (bool, error) {return true, Nil}type stringvalidator struct {Min int        Max Int}func (v stringvalidator) Validate (Val interface{}) (bool, error) {l: = Len (String)) if L = = 0 { return False, FMT. Errorf ("cannot Be blank")} if L < V.min {return false, FMT. Errorf ("Should is at least%v chars long", v.min)} if V.max >= v.min && l > V.max {return fal SE, FMT. Errorf ("Should is less than%v chars long", V.max)} return True, Nil}type numbervalidator struct {Min int Ma X Int}func (v numbervalidator) Validate (Val interface{}) (bool, error) {num: = val. (int) If num < v.min {        return False, FMT. Errorf ("should be greater than%v", v.min)} if V.max >= v.min && num > V.max {return false, F Mt. Errorf ("Should is less than%v", V.max)} return True, Nil}type emailvalidator struct {}func (v emailvalidator) Vali Date (Val interface{}) (bool, error) {if!mailre.matchstring (val. (string)) {return false, FMT. Errorf ("is not a valid email address")} return True, Nil}func Getvalidatorfromtag (tag string) Validator {args: = Strings. Split (Tag, ",") switch Args[0] {case "number": Validator: = numbervalidator{}//resolves min and Max in Structtag Into the structure of FMT. SSCANF (Strings. Join (args[1:], ","), "min=%d,max=%d", &validator. Min, &validator. MAX) Return validator Case "string": Validator: = stringvalidator{} fmt. SSCANF (Strings. Join (args[1:], ","), "min=%d,max=%d", &validator. Min, &validator.   MAX) return validator case "Email": Return emailvalidator{} } return Defaultvalidator{}}func validatestruct (S interface{}) []error {errs: = []error{} V: = reflect. ValueOf (s) for i: = 0; I < V.numfield (); i++ {//Use reflection to get structtag tag: = V.type (). Field (i). Tag.get (tagName) if tag = = "" | | Tag = = "-" {continue} Validator: = Getvalidatorfromtag (tag) valid, err: = Validator. Validate (V.field (i). Interface ()) if!valid && err! = Nil {errs = append (errs, FMT. Errorf ("%s%s", V.type (). Field (i). Name, Err. Error ())}} return Errs}type User struct {Id int ' validate: "number,min=1,max=1000" ' Name stri    Ng ' Validate: ' string,min=2,max=10 ' ' Bio string ' validate: ' String ' ' email string ' Validate: ' email ' '}func main () { User: = user{id:0, Name: "Superlongstring", Bio: "", Email: "Foobar", "FMT". Println ("Errors:") for I, err: = Range validatestruct (user) {fmt. Printf ("\t%d%s\n", I+1, Err. Error ())}}

The code is well understood, the structure is very clear, do not explain too much ^_^

On GitHub There is already a ready-made verification package for Govalidator, which supports built-in support for validation tags and custom validation tags:

package mainimport (    "github.com/asaskevich/govalidator"    "fmt"    "strings")type Server struct {    ID         string `valid:"uuid,required"`    Name       string `valid:"machine_id"`    HostIP     string `valid:"ip"`    MacAddress string `valid:"mac,required"`    WebAddress string `valid:"url"`    AdminEmail string `valid:"email"`}func main() {    server := &Server{        ID:         "123e4567-e89b-12d3-a456-426655440000",        Name:       "IX01",        HostIP:     "127.0.0.1",        MacAddress: "01:23:45:67:89:ab",        WebAddress: "www.example.com",        AdminEmail: "admin@exmaple.com",    }    //自定义tag验证函数    govalidator.TagMap["machine_id"] = govalidator.Validator(func(str string) bool {        return strings.HasPrefix(str, "IX")    })    if ok, err := govalidator.ValidateStruct(server); err != nil {        panic(err)    } else {        fmt.Printf("OK: %v\n", ok)    }}

Resources:

    • Custom struct field tags in golang
    • Data validation in Golang
    • govalidator
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.