在Go编程中,反射(Reflection)是一项强大而复杂的特性,它允许程序在运行时检查和修改自己的结构和行为。虽然在日常编程中我们可能会尽量避免过度使用反射(因为它会带来复杂性和性能损失),但了解反射机制对于理解许多高级库和框架的工作原理至关重要。
反射是程序在运行时访问、检查和修改其自身结构的能力。在Go中,反射主要通过reflect
包提供支持,它使我们能够:
反射在某种程度上打破了Go的静态类型系统,为程序提供了更高的灵活性,但同时也带来了复杂性和潜在的运行时错误。
理解Go反射的核心是掌握Rob Pike提出的"反射三大法则":
在某些场景下,反射是非常有用甚至是必不可少的:
Go的反射机制主要通过reflect
包中的两个核心类型来实现:
这两个类型是Go反射机制的基础,几乎所有反射操作都围绕它们展开。
Type
表示具体的类型,而Kind
表示类型的基础类别。例如:
*User
和*Admin
是不同的Type
,但它们的Kind
都是Ptr
[]int
和[]string
是不同的Type
,但它们的Kind
都是Slice
Go的反射系统定义了23种基本Kind:
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
)
Value
类型表示一个Go值,它提供了访问和修改值的方法:
package main
import (
"fmt"
"reflect"
)
func main() {
// 通过ValueOf获取值的反射表示
var x int = 100
v := reflect.ValueOf(x)
fmt.Println("值:", v.Interface()) // 输出: 100
fmt.Println("类型:", v.Type()) // 输出: int
fmt.Println("种类:", v.Kind()) // 输出: int
fmt.Println("是否可寻址:", v.CanAddr()) // 输出: false (传值方式)
fmt.Println("是否可设置:", v.CanSet()) // 输出: false (传值方式)
// 创建可设置的Value
y := 200
pv := reflect.ValueOf(&y) // 获取y的地址的反射值
ev := pv.Elem() // 获取指针指向的Value
fmt.Println("是否可寻址:", ev.CanAddr()) // 输出: true
fmt.Println("是否可设置:", ev.CanSet()) // 输出: true
// 修改值
if ev.CanSet() {
ev.SetInt(300)
fmt.Println("修改后的y值:", y) // 输出: 300
}
// 结构体值
type Person struct {
Name string
Age int
}
p := Person{"李四", 25}
pv = reflect.ValueOf(&p)
ev = pv.Elem()
// 获取并修改字段值
nameField := ev.FieldByName("Name")
if nameField.CanSet() {
nameField.SetString("王五")
}
ageField := ev.FieldByName("Age")
if ageField.CanSet() {
ageField.SetInt(30)
}
fmt.Printf("修改后的person: %+v\n", p) // 输出: {Name:王五 Age:30}
}
Value
类型常用方法:
Interface()
:将Value转换回接口值Type()
:返回Value的类型Kind()
:返回Value的种类CanAddr()
:报告Value是否可寻址CanSet()
:报告Value是否可设置Elem()
:返回指针指向的ValueField(i int)
:返回结构体的第i
个字段的ValueFieldByName(name string)
:通过名称返回结构体字段的ValueMethod(i int)
:返回类型的第i
个方法的ValueIsValid()
:检查通过反射获得的反射对象是否有效。反射的第一法则是"从接口值到反射对象"。在Go中,这通过两个基本函数实现:
reflect.TypeOf()
:返回一个reflect.Type
,表示值的类型reflect.ValueOf()
:返回一个reflect.Value
,包含值的实际数据当我们传递一个值给reflect.TypeOf()
或reflect.ValueOf()
时,该值会被转换为一个空接口interface{}
,然后反射系统从这个接口值中提取类型和值信息。
package main
import (
"fmt"
"reflect"
)
func main() {
// 基本类型
i := 42
s := "Hello, reflection!"
// 获取类型信息
iType := reflect.TypeOf(i)
sType := reflect.TypeOf(s)
fmt.Printf("Type of i: %v\n", iType) // int
fmt.Printf("Type of s: %v\n", sType) // string
// 获取种类信息
fmt.Printf("Kind of i: %v\n", iType.Kind()) // int
fmt.Printf("Kind of s: %v\n", sType.Kind()) // string
// 获取值信息
iValue := reflect.ValueOf(i)
sValue := reflect.ValueOf(s)
fmt.Printf("Value of i: %v\n", iValue) // 42
fmt.Printf("Value of s: %v\n", sValue) // Hello, reflection!
// 获取值的实际内容
fmt.Printf("Value of i (int): %v\n", iValue.Int()) // 42
fmt.Printf("Value of s (string): %v\n", sValue.String()) // Hello, reflection!
}
反射的第二法则是"从反射对象到接口值"。我们可以使用Interface()
方法将reflect.Value
转换回接口值:
这个过程是从接口值到反射对象的逆过程,将反射值转换回Go值。
package main
import (
"fmt"
"reflect"
)
func main() {
i := 42
v := reflect.ValueOf(i)
// 从反射值到接口值
iface := v.Interface()
// 使用类型断言获取具体类型的值
i2 := iface.(int)
fmt.Println(i2) // 输出: 42
}
反射的第三法则是"要修改反射对象,其值必须可设置(可寻址)"。简单来说,只有当原始值可以被修改时,其对应的反射值才能被修改。
reflect.ValueOf()
得到的反射值是不可设置的Elem()
方法得到的反射值是可设置的CanSet()
方法)package main
import (
"fmt"
"reflect"
)
func main() {
// 示例1: 无法修改不可设置的值
x := 42
v := reflect.ValueOf(x)
// 这会导致panic
// v.SetInt(21) // panic: reflect.Value.SetInt using unaddressable value
// 示例2: 修改可设置的值
y := 42
p := reflect.ValueOf(&y) // 获取y的地址的反射值
v = p.Elem() // 获取指针指向的值
fmt.Println("CanSet:", v.CanSet()) // true
v.SetInt(21)
fmt.Println(y) // 输出: 21
}
反射最常见的用途之一是检查变量类型的各个方面。特别是对于复杂的结构体类型,反射提供了强大的功能来检查其字段、方法和标签。
结构体是Go中最常用的复合类型之一。通过反射,我们可以检查结构体的字段、标签和方法:
package main
import (
"fmt"
"reflect"
)
type User struct {
ID int `json:"id" validate:"required"`
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=0,lte=130"`
CreatedAt string `json:"created_at" validate:"-"`
}
func (u User) GetFullInfo() string {
return fmt.Sprintf("User %s (ID: %d, Email: %s)", u.Name, u.ID, u.Email)
}
func (u *User) UpdateName(newName string) {
u.Name = newName
}
func inspectStruct(v interface{}) {
t := reflect.TypeOf(v)
// 如果是指针,获取其指向的元素类型
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
// 确保是结构体
if t.Kind() != reflect.Struct {
fmt.Println("不是结构体类型")
return
}
fmt.Printf("类型名称: %s\n", t.Name())
fmt.Printf("字段数量: %d\n", t.NumField())
fmt.Println("字段列表:")
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf(" %d: %s (%s)\n", i, field.Name, field.Type)
// 打印字段标签
if tag := field.Tag; tag != "" {
fmt.Printf(" 标签: %s\n", tag)
fmt.Printf(" json标签: %s\n", tag.Get("json"))
fmt.Printf(" validate标签: %s\n", tag.Get("validate"))
}
}
// 检查方法
fmt.Println("方法列表:")
// 值接收者方法
vt := reflect.TypeOf(v)
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
fmt.Printf(" 值接收者方法 %d: %s\n", i, method.Name)
fmt.Printf(" 类型: %s\n", method.Type)
}
// 指针接收者方法
if vt.Kind() != reflect.Ptr {
vt = reflect.PtrTo(t)
}
for i := 0; i < vt.NumMethod(); i++ {
method := vt.Method(i)
fmt.Printf(" 指针接收者方法 %d: %s\n", i, method.Name)
fmt.Printf(" 类型: %s\n", method.Type)
}
}
func main() {
user := User{
ID: 1,
Name: "张三",
Email: "zhangsan@example.com",
Age: 30,
CreatedAt: "2023-01-01",
}
inspectStruct(user)
}
反射还可以用来检查一个类型是否实现了特定接口:
package main
import (
"fmt"
"io"
"os"
"reflect"
)
// 检查类型是否实现接口
func implementsInterface(v interface{}, interfaceType reflect.Type) bool {
vType := reflect.TypeOf(v)
return vType.Implements(interfaceType)
}
// 获取接口类型
func getInterfaceType(interfaceValue interface{}) reflect.Type {
return reflect.TypeOf(interfaceValue).Elem()
}
func main() {
// 检查*os.File是否实现io.Reader接口
file, _ := os.Open("example.txt")
defer file.Close()
var reader io.Reader
readerType := getInterfaceType(&reader)
fmt.Printf("*os.File是否实现io.Reader: %v\n",
implementsInterface(file, readerType))
// 检查string是否实现io.Reader
var s string = "hello"
fmt.Printf("string是否实现io.Reader: %v\n",
implementsInterface(s, readerType))
}
除了检查类型和值外,反射还允许我们在运行时修改变量的值,只要这些值是可寻址和可设置的。
var x int = 100
v := reflect.ValueOf(&x) // 获取指针的反射值
e := v.Elem() // 获取指针指向的值
fmt.Println("旧值:", x)
e.SetInt(200)
fmt.Println("新值:", x)
package main
import (
"fmt"
"reflect"
)
type Product struct {
ID int
Name string
Price float64
Quantity int
Tags []string
Metadata map[string]string
}
func main() {
p := Product{
ID: 1,
Name: "笔记本电脑",
Price: 5999.99,
Quantity: 10,
Tags: []string{"电子", "计算机"},
Metadata: map[string]string{"brand": "品牌A", "color": "银色"},
}
fmt.Printf("原始产品: %+v\n", p)
// 获取可设置的反射值
v := reflect.ValueOf(&p).Elem()
// 修改基本类型字段
if idField := v.FieldByName("ID"); idField.IsValid() && idField.CanSet() {
idField.SetInt(2)
}
if nameField := v.FieldByName("Name"); nameField.IsValid() && nameField.CanSet() {
nameField.SetString("高性能服务器")
}
if priceField := v.FieldByName("Price"); priceField.IsValid() && priceField.CanSet() {
priceField.SetFloat(19999.99)
}
// 修改切片字段
if tagsField := v.FieldByName("Tags"); tagsField.IsValid() && tagsField.CanSet() {
// 创建一个新的切片
newTags := reflect.MakeSlice(tagsField.Type(), 3, 3)
newTags.Index(0).SetString("服务器")
newTags.Index(1).SetString("企业级")
newTags.Index(2).SetString("高性能")
// 设置整个切片
tagsField.Set(newTags)
}
// 修改映射字段
if metaField := v.FieldByName("Metadata"); metaField.IsValid() && metaField.CanSet() {
// 创建一个新的映射
newMeta := reflect.MakeMap(metaField.Type())
newMeta.SetMapIndex(reflect.ValueOf("brand"), reflect.ValueOf("品牌B"))
newMeta.SetMapIndex(reflect.ValueOf("color"), reflect.ValueOf("黑色"))
newMeta.SetMapIndex(reflect.ValueOf("series"), reflect.ValueOf("专业系列"))
// 设置整个映射
metaField.Set(newMeta)
}
fmt.Printf("修改后的产品: %+v\n", p)
}
反射提供了创建新值的能力,这在需要动态创建对象时非常有用:
package main
import (
"fmt"
"reflect"
)
func main() {
// 创建基本类型值
intType := reflect.TypeOf(0)
intValue := reflect.New(intType).Elem() // 创建int类型的可设置Value
intValue.SetInt(42)
fmt.Println("创建的int值:", intValue.Interface())
// 创建切片
sliceType := reflect.TypeOf([]string{})
sliceValue := reflect.MakeSlice(sliceType, 3, 5) // 长度3,容量5
// 设置切片元素
sliceValue.Index(0).SetString("Go")
sliceValue.Index(1).SetString("Java")
sliceValue.Index(2).SetString("Python")
fmt.Println("创建的切片:", sliceValue.Interface())
// 创建映射
mapType := reflect.TypeOf(map[string]int{})
mapValue := reflect.MakeMap(mapType)
// 添加键值对
mapValue.SetMapIndex(reflect.ValueOf("one"), reflect.ValueOf(1))
mapValue.SetMapIndex(reflect.ValueOf("two"), reflect.ValueOf(2))
mapValue.SetMapIndex(reflect.ValueOf("three"), reflect.ValueOf(3))
fmt.Println("创建的映射:", mapValue.Interface())
// 创建结构体
type Person struct {
Name string
Age int
}
personType := reflect.TypeOf(Person{})
personPtr := reflect.New(personType) // 创建*Person类型的Value
personValue := personPtr.Elem() // 获取Person类型的Value
personValue.FieldByName("Name").SetString("张三")
personValue.FieldByName("Age").SetInt(30)
fmt.Println("创建的结构体:", personValue.Interface())
// 将反射值转换为具体类型
person := personPtr.Interface().(*Person)
fmt.Printf("转换后的Person: %+v\n", *person)
}
反射的另一个强大功能是能够在运行时动态调用函数和方法。
通过反射调用函数需要准备好函数和参数的反射值:
package main
import (
"fmt"
"reflect"
)
func add(a, b int) int {
return a + b
}
func concat(strs ...string) string {
result := ""
for _, s := range strs {
result += s
}
return result
}
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}
func callFunction() {
// 获取函数的反射值
addFunc := reflect.ValueOf(add)
// 准备参数
args := []reflect.Value{
reflect.ValueOf(10),
reflect.ValueOf(20),
}
// 调用函数
results := addFunc.Call(args)
// 处理返回值
if len(results) > 0 {
fmt.Println("add(10, 20) =", results[0].Interface())
}
// 调用可变参数函数
concatFunc := reflect.ValueOf(concat)
concatArgs := []reflect.Value{
reflect.ValueOf("Hello"),
reflect.ValueOf(", "),
reflect.ValueOf("World"),
reflect.ValueOf("!"),
}
concatResults := concatFunc.Call(concatArgs)
if len(concatResults) > 0 {
fmt.Println("concat结果:", concatResults[0].Interface())
}
// 调用返回多个值的函数
divideFunc := reflect.ValueOf(divide)
divideArgs := []reflect.Value{
reflect.ValueOf(10.0),
reflect.ValueOf(2.0),
}
divideResults := divideFunc.Call(divideArgs)
if len(divideResults) > 0 {
fmt.Printf("除法结果: %.2f\n", divideResults[0].Interface())
// 检查错误
if len(divideResults) > 1 && !divideResults[1].IsNil() {
fmt.Println("错误:", divideResults[1].Interface())
}
}
}
func main() {
callFunction()
}
调用结构体的方法与调用函数类似,但需要先获取方法的反射值:
package main
import (
"fmt"
"reflect"
)
type Calculator struct {
Brand string
}
func (c Calculator) Add(a, b int) int {
return a + b
}
func (c Calculator) Multiply(a, b int) int {
return a * b
}
func (c *Calculator) SetBrand(brand string) {
c.Brand = brand
}
func (c Calculator) GetInfo() string {
return fmt.Sprintf("Calculator [%s]", c.Brand)
}
func callMethod() {
// 创建计算器实例
calc := Calculator{Brand: "科学计算器"}
fmt.Println("原始计算器:", calc)
// 获取值接收者方法
addMethod := reflect.ValueOf(calc).MethodByName("Add")
if addMethod.IsValid() {
args := []reflect.Value{
reflect.ValueOf(10),
reflect.ValueOf(20),
}
result := addMethod.Call(args)
fmt.Println("Add方法结果:", result[0].Interface())
}
// 获取指针接收者方法 - 需要传递指针的反射值
calcPtr := reflect.ValueOf(&calc)
setBrandMethod := calcPtr.MethodByName("SetBrand")
if setBrandMethod.IsValid() {
args := []reflect.Value{
reflect.ValueOf("高级科学计算器"),
}
setBrandMethod.Call(args)
fmt.Println("修改后的计算器:", calc)
}
// 动态选择要调用的方法
methodName := "Multiply"
method := reflect.ValueOf(calc).MethodByName(methodName)
if method.IsValid() {
args := []reflect.Value{
reflect.ValueOf(5),
reflect.ValueOf(7),
}
result := method.Call(args)
fmt.Printf("%s方法结果: %v\n", methodName, result[0].Interface())
} else {
fmt.Printf("方法%s不存在\n", methodName)
}
}
func main() {
callMethod()
}
Go标准库中的encoding/json
包使用反射来实现JSON的序列化(Marshal)和反序列化(Unmarshal)。这使得任何符合特定规则的Go结构体都可以自动转换为JSON,而无需为每种结构编写专门的编码/解码代码。
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
Addresses []string `json:"addresses"`
private string // 不会被序列化
}
func main() {
person := Person{
Name: "张三",
Age: 30,
Addresses: []string{"北京市", "上海市"},
private: "私有字段",
}
// 序列化为JSON
data, err := json.Marshal(person)
if err != nil {
fmt.Println("序列化错误:", err)
return
}
fmt.Println("JSON:", string(data))
// 反序列化
var decodedPerson Person
err = json.Unmarshal(data, &decodedPerson)
if err != nil {
fmt.Println("反序列化错误:", err)
return
}
fmt.Printf("反序列化结果: %+v\n", decodedPerson)
}
许多验证库,如validator
,使用反射来根据结构体标签验证字段值:
package main
import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
// 验证器接口
type Validator interface {
Validate(val interface{}) bool
Message() string
}
// 电子邮件验证器
type EmailValidator struct{}
func (v EmailValidator) Validate(val interface{}) bool {
str, ok := val.(string)
if !ok {
return false
}
return regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`).MatchString(str)
}
func (v EmailValidator) Message() string {
return "必须是有效的电子邮件地址"
}
// 长度验证器
type LengthValidator struct {
Min int
Max int
}
func (v LengthValidator) Validate(val interface{}) bool {
str, ok := val.(string)
if !ok {
return false
}
length := len(str)
if v.Min > 0 && length < v.Min {
return false
}
if v.Max > 0 && length > v.Max {
return false
}
return true
}
func (v LengthValidator) Message() string {
if v.Min > 0 && v.Max > 0 {
return fmt.Sprintf("长度必须在%d到%d之间", v.Min, v.Max)
}
if v.Min > 0 {
return fmt.Sprintf("长度必须大于等于%d", v.Min)
}
return fmt.Sprintf("长度必须小于等于%d", v.Max)
}
// 验证结构体
func ValidateStruct(obj interface{}) []string {
var errors []string
v := reflect.ValueOf(obj)
// 处理指针
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// 确保是结构体
if v.Kind() != reflect.Struct {
return []string{"只能验证结构体"}
}
t := v.Type()
// 遍历所有字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// 检查validate标签
validateTag := field.Tag.Get("validate")
if validateTag == "" {
continue
}
fieldValue := v.Field(i)
// 解析验证规则
validations := strings.Split(validateTag, ",")
for _, validation := range validations {
var validator Validator
if validation == "email" {
validator = EmailValidator{}
} else if strings.HasPrefix(validation, "length=") {
params := strings.TrimPrefix(validation, "length=")
parts := strings.Split(params, ":")
min, _ := strconv.Atoi(parts[0])
max := 0
if len(parts) > 1 {
max, _ = strconv.Atoi(parts[1])
}
validator = LengthValidator{Min: min, Max: max}
} else {
continue
}
// 验证字段值
if !validator.Validate(fieldValue.Interface()) {
errors = append(errors,
fmt.Sprintf("字段 %s: %s", field.Name, validator.Message()))
}
}
}
return errors
}
func main() {
type User struct {
Username string `validate:"length=3:20"`
Email string `validate:"email"`
Bio string `validate:"length=0:100"`
}
user := User{
Username: "a",
Email: "invalid-email",
Bio: strings.Repeat("很长的简介 ", 30),
}
errors := ValidateStruct(user)
if len(errors) > 0 {
fmt.Println("验证错误:")
for _, err := range errors {
fmt.Println("- " + err)
}
} else {
fmt.Println("验证通过")
}
}
反射可用于实现依赖注入系统,在运行时动态装配对象:
package main
import (
"fmt"
"reflect"
)
// 服务接口
type Logger interface {
Log(message string)
}
type UserRepository interface {
FindUser(id int) string
}
// 服务实现
type ConsoleLogger struct{}
func (l *ConsoleLogger) Log(message string) {
fmt.Printf("[LOG] %s\n", message)
}
type DatabaseUserRepository struct {
Logger Logger `inject:"logger"`
}
func (r *DatabaseUserRepository) FindUser(id int) string {
r.Logger.Log(fmt.Sprintf("查找用户ID: %d", id))
return fmt.Sprintf("用户_%d", id)
}
// 控制器
type UserController struct {
Repository UserRepository `inject:"userRepository"`
Logger Logger `inject:"logger"`
}
func (c *UserController) GetUser(id int) {
c.Logger.Log(fmt.Sprintf("控制器处理GetUser请求,ID: %d", id))
user := c.Repository.FindUser(id)
fmt.Printf("找到用户: %s\n", user)
}
// 简单的依赖注入容器
type Container struct {
services map[string]interface{}
}
func NewContainer() *Container {
return &Container{
services: make(map[string]interface{}),
}
}
func (c *Container) Register(name string, service interface{}) {
c.services[name] = service
}
func (c *Container) Resolve(target interface{}) error {
v := reflect.ValueOf(target)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return fmt.Errorf("目标必须是结构体指针")
}
e := v.Elem()
t := e.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
injectTag := field.Tag.Get("inject")
if injectTag == "" {
continue
}
service, exists := c.services[injectTag]
if !exists {
return fmt.Errorf("服务不存在: %s", injectTag)
}
sv := reflect.ValueOf(service)
fieldValue := e.Field(i)
if !sv.Type().AssignableTo(fieldValue.Type()) {
return fmt.Errorf(
"类型不匹配: 服务 %s 类型为 %s, 但字段 %s 类型为 %s",
injectTag, sv.Type(), field.Name, fieldValue.Type(),
)
}
fieldValue.Set(sv)
// 递归解析依赖
if sv.Kind() == reflect.Ptr && sv.Elem().Kind() == reflect.Struct {
c.Resolve(service)
}
}
return nil
}
func main() {
// 创建容器
container := NewContainer()
// 注册服务
logger := &ConsoleLogger{}
repository := &DatabaseUserRepository{}
controller := &UserController{}
container.Register("logger", logger)
container.Register("userRepository", repository)
// 解析依赖
if err := container.Resolve(repository); err != nil {
fmt.Printf("解析仓库依赖错误: %v\n", err)
return
}
if err := container.Resolve(controller); err != nil {
fmt.Printf("解析控制器依赖错误: %v\n", err)
return
}
// 使用控制器
controller.GetUser(42)
}
通过 reflect.Type
获取结构体成员信息 reflect.StructField
结构中的 Tag 被称为结构体标签(StructTag)。结构体标签是对结构体字段的额外信息标签。
JSON、BSON 等格式进行序列化及对象关系映射(Object Relational Mapping,简称 ORM)系统都会用到结构体标签,这些系统使用标签设定字段在处理时应该具备的特殊属性和可能发生的行为。这些信息都是静态的,无须实例化结构体,可以通过反射获取到。
StructTag具有两个方法
func (tag StructTag) Get(key string) string //根据 Tag 中的键获取对应的值
func (tag StructTag) Lookup(key string) (value string, ok bool) //根据 Tag 中的键,查询值是否存在
type Stu struct {
name string `json:"name" yaml:"n"`
age int `json:"age" yaml:"a"`
//表示json序列化的时候该字段忽略
}
s := Stu{name: "lucy", age: 18}
typ := reflect.TypeOf(s)
tag1 := typ.Field(0).Tag
tag2 := typ.Field(1).Tag
//name字段Tag为,json:name,yaml:n
fmt.Printf("name字段Tag为,json:%s,yaml:%s\n", tag1.Get("json"), tag1.Get("yaml"))
//age字段Tag为,json:age,yaml:a
fmt.Printf("age字段Tag为,json:%s,yaml:%s\n", tag2.Get("json"), tag2.Get("yaml"))
JSON
`json:"fieldname,omitempty"` // omitempty表示值为零值时忽略
DB
`db:"column_name"`
表单
`form:"field_name"`
验证
`validate:"required,min=3,max=50"`