本文同时发表在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. csv格式订单下载,完成后伴随邮件通知下载

    前言 功能开发中会遇到大量订单下载,而服务器的请求响应时间又配置的很短,导致下载时候请求超时. 这篇文章主要思路:异步查询数据,生成csv文件,放入email中并发送给用户.(异步部分本文不做介绍,配 ...

  2. git学习之创建版本库

    创建版本库 什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改.删除,Git都能跟踪,以便任何时刻都可以 ...

  3. android shape 大全 (转)

    1. 各属性的配置语法 在项目 res/drawable 文件夹中创建一个以 shape 为根节点的 XML 文件,基本语法如下: <?xml version="1.0" e ...

  4. 从ThoughtWorks 2017技术雷达看微软技术

    ThoughtWorks在每年都会出品两期技术雷达,这是一份关于技术趋势的报告,它比起一些我们能在市面上见到的其他各种技术行情和预测报告,更加具体,更具可操作性,因为它不仅涉及到新技术大趋势,比如云平 ...

  5. 关于 use-default-filters 属性的说明

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7940755.html ------------------------------------ ...

  6. 数细胞-swust oj

    数细胞(0964) 一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数.编程需要用到的队列及其相关函数已经实现,你只需要完 ...

  7. centos7.3搭建lamp实现使用wordpress

    ``` 环境说明: 在同一台主机上实现LAMP(Linux + Apache + MariaDB + PHP) CentOS 7.3.Apache 2.4.6.MariaDB 5.5.52.PHP 5 ...

  8. Delphi 7中对StretchBlt, StretchDIBits, DrawDibDraw, BitBlt 的性能测试 - 原创

    我的天哪,上一篇博文是2年前的事情了.看来又虚度了2年光阴,继续学习... 本文算是副产品,正品是利用FFmpeg从任意视频中生成GIF片段的小程序,等写完了再发.不为别的,只是为了给儿子做动图,且看 ...

  9. Dubbo源码学习--服务发布(ProxyFactory、Invoker)

    上文分析了Dubbo服务发布的整体流程,但服务代理生成的具体细节介绍得还不是很详细.下面将会接着上文继续分析.上文介绍了服务代理生成的切入点,如下: Invoker<?> invoker ...

  10. pku 1061 青蛙的约会 扩展欧几里得

    青蛙的约会Time Limit: 1000MS Memory Limit: 10000KTotal Submissions: 120482 Accepted: 25449Description 两只青 ...