最近在看 gin 的源码,看完了路由部分,不知道该看些什么,所以跑到 gin下的 issues 下看下别人有问题。正好看到了一个 Bind 的实例,就研究了下
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
student := &struct {
Name []string `form:"name"`
}{}
if err := c.Bind(student); err != nil {
c.JSON(http.StatusInternalServerError, err)
}else {
c.JSON(http.StatusOK, student)
}
})
r.Run() // listen and serve on 0.0.0.0:8080
}
在命令行中的输入输出结果如下
结构体被赋值的流程
通过输入和输出可以知道Bind的功能是把命令行中的参数正确的赋值给一个匿名的结构体。那么这个过程究竟是如何实现的呢?
func (formBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
if err := req.ParseMultipartForm(defaultMemory); err != nil {
if err != http.ErrNotMultipart {
return err
}
}
if err := mapForm(obj, req.Form); err != nil {
return err
}
return validate(obj)
}
通过Bind源码可以知道,首先是读取表单,然后使用mapForm函数来利用表单的数据初始化obj。表单的数据是如何处理的这里就暂不追究细节了,但是通过mapForm的签名可以知道req.Form的类型是map[string][]string。
func mapForm(ptr interface{}, form map[string][]string) error {
return mapFormByTag(ptr, form, "form")
}
mapFormByTag的名称就可以表示这个函数的作用了,利用tag来完成form到ptr的映射,mapForm则就是使用form这个tag来完成映射操作的。这个时候问题就来了,tag是什么?我们可以通过反射来获取结构体的属性的tag,这样就可以通过结构体属性的tag来从form取值并赋值给结构体属性。
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name []string `form:"name"`
Age int `form:"age"`
}
func main() {
user := Student{Name: []string{"zhangsan", "lisi"}, Age: 10}
tValue := reflect.TypeOf(user)
for i:=0; i<tValue.NumField(); i++ {
fmt.Println(tValue.Field(i).Tag.Get("form"))
}
// Output: name
// age
}
结构体被赋值的细节
下面就直接进入到处理的函数部分了,总的来说这就是不断递归的操作。
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
if field.Tag.Get(tag) == "-" { // just ignoring this field
return false, nil
}
var vKind = value.Kind()
if vKind == reflect.Ptr {
var isNew bool
vPtr := value
if value.IsNil() {
isNew = true
vPtr = reflect.New(value.Type().Elem())
}
isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
if err != nil {
return false, err
}
if isNew && isSetted {
value.Set(vPtr)
}
return isSetted, nil
}
if vKind != reflect.Struct || !field.Anonymous {
ok, err := tryToSetValue(value, field, setter, tag)
if err != nil {
return false, err
}
if ok {
return true, nil
}
}
if vKind == reflect.Struct {
tValue := value.Type()
var isSetted bool
for i := 0; i < value.NumField(); i++ {
sf := tValue.Field(i)
if sf.PkgPath != "" && !sf.Anonymous { // unexported
continue
}
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
if err != nil {
return false, err
}
isSetted = isSetted || ok
}
return isSetted, nil
}
return false, nil
}
查看Kind的基本类型如下,可以知道除了指针和结构体,就属于 go 中的基本数据类型了。在vKind != reflect.Struct && vKind != reflect.Ptr的时候,就可以通过基本类型来对结构体属性进行赋值。
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
对结构体的基本类型的赋值操作如下,这部分的操作是解析tag。
- 如果
tag是空字符串,那么会给tag一个默认值,是结构体的属性的名称。 - 如果
tag提供了默认值,那么在setter中没有这个tag的时候,会给结构体属性提供一个默认值。
func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
var tagValue string
var setOpt setOptions
tagValue = field.Tag.Get(tag)
tagValue, opts := head(tagValue, ",")
if tagValue == "" { // default value is FieldName
tagValue = field.Name
}
if tagValue == "" { // when field is "emptyField" variable
return false, nil
}
var opt string
for len(opts) > 0 {
opt, opts = head(opts, ",")
if k, v := head(opt, "="); k == "default" {
setOpt.isDefaultExists = true
setOpt.defaultValue = v
}
}
return setter.TrySet(value, field, tagValue, setOpt)
}
设置默认值的操作可以如下配置,请求的 url 中并没有传递参数 age。对于不设置的tag的用法,大家可以自己尝试。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
student := &struct {
Name []string `form:"name"`
Age int `form:"age,default=10"`
}{}
if err := c.Bind(student); err != nil {
c.JSON(http.StatusInternalServerError, err)
} else {
c.JSON(http.StatusOK, student)
}
})
r.Run() // listen and serve on 0.0.0.0:8080
}
好了,接下来就要最后一步了,对结构体属性进行赋值:
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
vs, ok := form[tagValue]
if !ok && !opt.isDefaultExists {
return false, nil
}
switch value.Kind() {
case reflect.Slice:
if !ok {
vs = []string{opt.defaultValue}
}
return true, setSlice(vs, value, field)
case reflect.Array:
if !ok {
vs = []string{opt.defaultValue}
}
if len(vs) != value.Len() {
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
}
return true, setArray(vs, value, field)
default:
var val string
if !ok {
val = opt.defaultValue
}
if len(vs) > 0 {
val = vs[0]
}
return true, setWithProperType(val, value, field)
}
}
在结构体属性为指针类型的时候,会稍微复杂一点,写个简单的代码表示下整体的流程。其他部分的代码就是一些校验和细节的考量了。
package main
import (
"fmt"
"reflect"
"testing"
)
type Student struct {
Name []string `form:"name"`
Age *int `form:"age"`
}
func main() {
user := &Student{}
userValue := reflect.ValueOf(user).Elem()
age := userValue.Field(1)
fmt.Println("age is nil ", age.Kind(), age.IsNil())
vPtr := reflect.New(age.Type().Elem())
vPtr.Elem().SetInt(10)
age.Set(vPtr)
fmt.Println("updated user is ", *user.Age)
// Output: 10
}