本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/125

假设我们有如下结构体:

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

我们需要对结构体内的字段进行验证合法性:

  • Id的值在某一个范围内。
  • Name的长度在某一个范围内。
  • Email格式正确。

我们可能会这么写:

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
}

这样的话代码比较冗余,而且如果结构体新加字段,还需要再修改验证函数再加一段if判断。这样代码比较冗余。我们可以借助golang的structTag来解决上述的问题:

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"就是structTag。如果对这个比较陌生的话,看看下面这个:


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的基本都用过json:xxx这个用法,json:xxx其实也是一个structTag,只不过这是golang帮你实现好特定用法的structTag。而validate:"number,min=1,max=1000"是我们自定义的structTag。

实现思路

我们定义一个接口Validator,定义一个方法Validate。再定义有具体意义的验证器例如StringValidatorNumberValidatorEmailValidator来实现接口Validator

这里为什么要使用接口?假设我们不使用接口代码会怎么写?

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

这样的话判断逻辑不能写在一个函数中,因为返回值validator会因为structTag的不同而不同,而且validator也不能当做函数参数做传递。而我们定义一个接口,所有的validator都去实现这个接口,上述的问题就能解决,而且逻辑更加清晰和紧凑。

关于接口的使用可以看下标准库的io Writer,Writer是个interface,只有一个方法Writer:

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

而输出函数可以直接调用参数的Write方法即可,无需关心到底是写到文件还是写到标准输出:

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.Buffer
Fprintf(&buf, "[") //写入到Buffer的缓存中

言归正传,我们看下完整代码,代码是Custom struct field tags in Golang中给出的:

package main

import (
"fmt"
"reflect"
"regexp"
"strings"
) const tagName = "validate" //邮箱验证正则
var mailRe = regexp.MustCompile(`\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z`) //验证接口
type Validator interface {
Validate(interface{}) (bool, error)
} type DefaultValidator struct {
} func (v DefaultValidator) 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(val.(string)) if l == 0 {
return false, fmt.Errorf("cannot be blank")
} if l < v.Min {
return false, fmt.Errorf("should be at least %v chars long", v.Min)
} if v.Max >= v.Min && l > v.Max {
return false, fmt.Errorf("should be less than %v chars long", v.Max)
} return true, nil
} type NumberValidator struct {
Min int
Max 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, fmt.Errorf("should be less than %v", v.Max)
} return true, nil
} type EmailValidator struct {
} func (v EmailValidator) Validate(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{}
//将structTag中的min和max解析到结构体中
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++ {
//利用反射获取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 string `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())
}
}

代码很好理解,结构也很清晰,不做过多解释了_

github上其实已经有现成的验证包了govalidator,支持内置支持的验证tag和自定义验证tag:

package main

import (
"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)
}
}

参考资料:

golang 如何验证struct字段的数据格式的更多相关文章

  1. golang自定义struct字段标签

    原文链接: https://sosedoff.com/2016/07/16/golang-struct-tags.html struct是golang中最常使用的变量类型之一,几乎每个地方都有使用,从 ...

  2. golang常用库:字段参数验证库-validator

    背景 在平常开发中,特别是在web应用开发中,为了验证输入字段的合法性,都会做一些验证操作.比如对用户提交的表单字段进行验证,或者对请求的API接口字段进行验证,验证字段的合法性,保证输入字段值的安全 ...

  3. Golang面向对象编程-struct(结构体)

    Golang面向对象编程-struct(结构体) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是面向对象编程 面向对象编程(Object Oriented Program ...

  4. .Net高级进阶,WebApi和MVC进行模型验证的时候,教你如何自由控制需要进行验证的字段?

    现在,你有一个MVC架构的web项目,你要完成一个注册功能. 前台传了3个值到你的控制器,分别是账号.密码.邮箱. 如图:现在你要在控制器里面判断,账号名称.密码.邮箱不能为空,并且名称和密码不超过1 ...

  5. django 表单验证和字段验证

    表单验证和字段验证 表单验证发生在数据验证之后.如果你需要自定义这个过程,有几个不同的地方可以修改,每个地方的目的不一样.表单处理过程中要运行三种类别的验证方法.它们通常在你调用表单的is_valid ...

  6. golang sqlx查询时, struct字段冲突

    type TA struct { Id int64 `db:"id"` } type TB struct { Id int64 `db:"id"` } type ...

  7. GO开发[五]:golang结构体struct

    Go结构体struct Go语言的结构体(struct)和其他语言的类(class)有同等的地位,但Go语言放弃了包括继承在内的大量面向对象特性,只保留了组合(composition)这个最基础的特性 ...

  8. MVC ValidationAttribute 验证一个字段必须大于另一个字段

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] pu ...

  9. Golang结构体struct的使用(结构体嵌套, 匿名结构体等)

    转自: https://studygolang.com/articles/11313 golang中是没有class的,但是有一个结构体struct,有点类似,他没有像java,c++中继承的概念,但 ...

随机推荐

  1. Ckeditor与Ckfinder的配合使用,上传图片、水印、修改图片名字为当前日期 asp.net

    为了配置出来上传功能,并且还添加水印,修改图片的名字为日期,真的头疼了很久,现在来分享一下自己所做的,也算一点小小的成就吧,顺带帮帮很多还在弄这个的猿们.我是分别用了两种方法.先说低版本的Versio ...

  2. StringUtils工具类常用方法汇总1(判空、转换、移除、替换、反转)

      Apache commons lang3包下的StringUtils工具类中封装了一些字符串操作的方法,非常实用,使用起来也非常方便.最近自己也经常在项目中使用到了里面的一些方法,在这里将常用的方 ...

  3. 一步步搭建Retrofit+RxJava+MVP网络请求框架(一)

    首先,展示一下封装好之后的项目的层级结构. 1.先创建一个RetrofitApiService.java package com.xdw.retrofitrxmvpdemo.http; import ...

  4. 解决tomcat部署包错误

    Context namespace element 'annotation-config' and its parser class [org.springframework.context.anno ...

  5. [转]git问题ERROR: Repository not found.的解决

    原文地址:http://blog.csdn.net/u010154424/article/details/51233966 在github中新增了一个项目,按照git的提示添加了远程仓库,但是提交的时 ...

  6. 制作多级菜单hide()与show() toggle()

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  7. Dagger2进阶必备技能

    之前写过一篇文章介绍Dagger2的初步知识, 本篇文章主要介绍Dagger2的进阶知识点. 主要包含的内有有 @Binds与@Provides的使用 Provider与Lazy的使用 依赖与包含 D ...

  8. tyvj4866 摆摊

    这分送的真慷慨,我随手打了个莫队,就90了.... 90分代码: #include<bits/stdc++.h> using namespace std; #define MAXN 100 ...

  9. JaveScript简单数据类型(JS知识点归纳二)

    JS中的简单数据类型有五种 : --> string --> number -->boolean --> null -->undefined 数据类型的检测 :typeo ...

  10. C++学习笔记24,方法重写与方法隐藏

    该博文仅用于交流学习.请慎用于不论什么商业用途.本博主保留对该博文的一切权利. 博主博客:http://blog.csdn.net/qq844352155 转载请注明出处: 方法重写.是指在子类中又一 ...