前言
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