前言
type Restaurant struct { Name string RestaurantId string `bson:"restaurant_id,omitempty"` Cuisine string `bson:"cuisine,omitempty"` Address interface{} `bson:"address,omitempty"` Borough string `bson:"borough,omitempty"` Grades []interface{} `bson:"grades,omitempty"`}最近在與 MongoDB 互動時發現 Struct 欄位結尾有一段語法不是很熟悉,這篇文章探討 Struct Tag 存在的原因以及解決什麼問題。
Struct Tag
用於標示 Struct 欄位的元資料(用於描述資料的資料)
Struct Tag 通常會在 runtime 執行時用做:「格式驗證」、「資料序列化」、「資料庫欄位應對」……等用途,可用於存儲任何資料沒有限制。與其命令式的操縱資料,透過宣告式的方式來描述資料格式是 Struct Tag 的主要用途。
| 用途 | 範例 |
|---|---|
| 資料驗證 | validate:"required,min=3" |
| 欄位權限 | auth:"admin_only" |
| 序列化格式 | csv:"username" |
| 對 ORM 的描述 | gorm:"primaryKey" |
| API export 控制 | expose:"false" |
Struct Tag 格式
Go 標準對 struct tag 的格式 有慣例但非強制:
key1:"value1" key2:"value2"value 裡常見分隔符:
,→ 參數列表,例如:json:"name,omitempty":→ key-value,例如:validate:"min=3"|→ 多條規則分隔,例如:rule:"a|b|c"(自訂風格)
Struct Tag 只是一串字串用於描述欄位,並不包含解析的邏輯,需要自己實踐透過 reflect 官方套件。
reflect 套件操作
reflect 套件用於觀察並修改數值於 runtime
基於 Go 是靜態型別的語言,在編譯期就決定所有型別的語言,透過 relfect 套件讀取 Struct Tag 並依其內容決定程式邏輯。
package main
import ( "fmt" "reflect")
func ValidateRequired(s any) error { v := reflect.ValueOf(s) t := reflect.TypeOf(s)
for i := 0; i < t.NumField(); i++ { field := t.Field(i) tag := field.Tag.Get("validate")
if tag == "required" { value := v.Field(i)
if value.IsZero() { return fmt.Errorf("field '%s' is required", field.Name) } } }
return nil}
func main() { type User struct { Name string `validate:"required"` }
u1 := User{Name: "John"} fmt.Println(ValidateRequired(u1)) // <nil>
u2 := User{} fmt.Println(ValidateRequired(u2)) // field 'Name' is required}總結
所以回到原先的問題,可以發現 MongoDB Driver for Go 使用 Struct Tags 背後其實是為了將資料應對為 bson 才需要額外添加這些 struct tag。
延伸閱讀
- What are the use(s) for struct tags in Go? - stackoverflow
- Use Struct Tags - MongoDB
- Creating custom struct tags in Golang is awesome! - Flo Woelk」」reating custom struct tags in Golang is awesome! - Flo Woelk