golang如何使用struct的tag屬性
golang如何使用struct的tag屬性
從一個例子說起
我們經常會碰到下面格式的struct定義:
type Person struct { Name string `json:"name"` Ageint`json:"age"` }
這個struct定義一個叫做Person的型別,包含兩個域Name和Age;但是在域的後面有神奇的json:"name"
,這個用來幹什麼用?這篇文章試圖來解釋這個問題。
當golang的物件需要和json做轉換的時候,我們就經常用到這個特性。
有兩點注意的地方:
- 如果一個域不是以大寫字母開頭的,那麼轉換成json的時候,這個域是被忽略的。
$ cat main.go package main import ( "fmt" "encoding/json" ) type Person struct { Name string `json:"name"` ageint`json:"age"` } func main() { person := Person { "tom", 12 } if b, err := json.Marshal(person); err != nil { fmt.Printf("error: %s", err.Error()) } else { fmt.Printf("value: %s", b) } } $ go build -o main main.go $ ./main value: {"name":"tom"}
我們看到轉換成json串之後,name正常輸出了,而age被丟棄了,因為age以小寫字母開頭。
-
如果沒有使用
json:"name"
tag,那麼輸出的json欄位名和域名是一樣的。
$ cat main.go package main import ( "fmt" "encoding/json" ) type Person struct { Name string Ageint } func main() { person := Person { "tom", 12 } if b, err := json.Marshal(person); err != nil { fmt.Printf("error: %s", err.Error()) } else { fmt.Printf("value: %s", b) } } $ go build -o main main.go $ ./main value: {"Name":"tom","Age":12}
我們看到輸出的json串使用的是struct定義的欄位名。
總結一下,json:"name"
格式串是用來指導json.Marshal/Unmarshal,在進行json串和golang物件之間轉換的時候對映欄位名使用的。再舉一個例子,json串和golang域名字可以任意轉換:
$ cat main.go package main import ( "fmt" "encoding/json" ) type Person struct { Name string`json:"age"` Ageint`json:"address"` } func main() { person := Person { "tom", 12 } if b, err := json.Marshal(person); err != nil { fmt.Printf("error: %s", err.Error()) } else { fmt.Printf("value: %s", b) } } $ go build -o main main.go $ ./main value: {"age":"tom","address":12}
這個例子我們把Name對映成了 age,而把Age對映成address,當然這是個奇葩的對映,沒有任何正向意義,只有負向意義,只是為了說明可以進行任何名字對映而已。
如果我們去看json包的原始碼,我可以看到在encoding/json/encode.go, encoding/json/decode.go裡面有讀取tag值得相關程式碼。
tag := sf.Tag.Get("json")
也就是說這個json的tag是被json.Marshal和json.Unmarshal來使用的。
我們如何使用tag
還是以前的例子,Person有一個域Age,我們能不能限定Age的值在1-100之間,不至於太大,否則這個值沒有意義了。
$ cat main.go package main import ( "fmt" "strings" "strconv" "reflect" _ "encoding/json" ) type Person struct { Name string`json:"name"` Ageint`json:"age" valid:"1-100"` } func (p * Person) validation() bool { v := reflect.ValueOf(*p) tag := v.Type().Field(1).Tag.Get("valid") val := v.Field(1).Interface().(int) fmt.Printf("tag=%v, val=%v\n", tag, val) result := strings.Split(tag, "-") var min, max int min, _ = strconv.Atoi(result[0]) max, _ = strconv.Atoi(result[1]) if val >= min && val <= max { return true } else { return false } } func main() { person1 := Person { "tom", 12 } if person1.validation() { fmt.Printf("person 1: valid\n") } else { fmt.Printf("person 1: invalid\n") } person2 := Person { "tom", 250 } if person2.validation() { fmt.Printf("person 2 valid\n") } else { fmt.Printf("person 2 invalid\n") } }
這麼例子我們給Person添加了一個validate函式,validate驗證age是不是合理。
這個函式可以擴充套件對任意struct的任意valid域進行驗證。
$ cat main.go package main import ( "fmt" "strings" "strconv" "reflect" _ "encoding/json" ) type Person struct { Name string`json:"name"` Ageint`json:"age" valid:"1-100"` } type OtherStruct struct { Ageint`valid:"20-300"` } func validateStruct(s interface{}) bool { v := reflect.ValueOf(s) for i := 0; i < v.NumField(); i++ { fieldTag:= v.Type().Field(i).Tag.Get("valid") fieldName:= v.Type().Field(i).Name fieldType:= v.Field(i).Type() fieldValue:= v.Field(i).Interface() if fieldTag == "" || fieldTag == "-" { continue } if fieldName == "Age" && fieldType.String() == "int" { val := fieldValue.(int) tmp := strings.Split(fieldTag, "-") var min, max int min, _ = strconv.Atoi(tmp[0]) max, _ = strconv.Atoi(tmp[1]) if val >= min && val <= max { return true } else { return false } } } return true } func main() { person1 := Person { "tom", 12 } if validateStruct(person1) { fmt.Printf("person 1: valid\n") } else { fmt.Printf("person 1: invalid\n") } person2 := Person { "jerry", 250 } if validateStruct(person2) { fmt.Printf("person 2: valid\n") } else { fmt.Printf("person 2: invalid\n") } other1 := OtherStruct { 12 } if validateStruct(other1) { fmt.Printf("other 1: valid\n") } else { fmt.Printf("other 1: invalid\n") } other2 := OtherStruct { 250 } if validateStruct(other2) { fmt.Printf("other 2: valid\n") } else { fmt.Printf("other 2: invalid\n") } }
在這個例子中我們定義了一個函式validateStruct,接受任意一個struct作為引數;validateStruct為驗證struct中所有定義的Age欄位,如果欄位名字是Age,欄位型別是int,並且定義了valid tag,那麼就會驗證這個valid是否有效。
看執行結果:
$ go build -o main main.go $ ./main person 1: valid person 2: invalid other 1: invalid other 2: valid