golang如何使用struct的tag屬性的詳細介紹

這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

從一個例子說起

我們經常會碰到下面格式的struct定義:

type Person struct {
  Name string `json:"name"`
  Age int  `json:"age"`
}

這個struct定義一個叫做Person的類型,包含兩個域Name和Age;但是在域的後面有神奇的 json:"name" ,這個用來幹什麼用?這篇文章試圖來解釋這個問題。

當golang的對象需要和json做轉換的時候,我們就經常用到這個特性。

有兩點注意的地方:

1、如果一個域不是以大寫字母開頭的,那麼轉換成json的時候,這個域是被忽略的。

$ cat main.go
package main

import (
  "fmt"
  "encoding/json"
)

type Person struct {
  Name string `json:"name"`
  age int  `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以小寫字母開頭。

2、如果沒有使用 json:"name" tag,那麼輸出的json字段名和域名是一樣的。

$ cat main.go
package main

import (
  "fmt"
  "encoding/json"
)

type Person struct {
  Name string
  Age int
}

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"`
  Age int    `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"`
  Age int    `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"`
  Age int    `json:"age" valid:"1-100"`
}

type OtherStruct struct {
  Age int    `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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持神馬文庫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章