目录
[2.1 json序列化与反序列化🌸](#2.1 json序列化与反序列化🌸)
[2.2 xml序列化与反序列化🌸](#2.2 xml序列化与反序列化🌸)
[2.3 binding序列化🌸](#2.3 binding序列化🌸)
[2.4 嵌套结构与标签组合🌸](#2.4 嵌套结构与标签组合🌸)
[3.结构体与反射 🌊](#3.结构体与反射 🌊)
📝前言
本文将主要介绍结构体标签(Tags)与基于reflect包的反射机制,这两个内容在网络编程中比较常用到。
1.反射的基础🌊
反射的核心是reflect.Type 和reflect.Value 类型,分别用于表示变量的类型信息 和值信息。
            
            
              Go
              
              
            
          
          	o := reflect.ValueOf("hello,world")   // 获取对象的值,即hello  world
	u := reflect.TypeOf("hello,world")    // 夺取对象的类型,即string
	fmt.Println(o, u) 
        🏆反射三大定律:
- 接口值到反射对象: 通过 TypeOf 和 ValueOf 将接口变量转换为反射对象。
 - **反射对象到接口值:**通过Value.Interface()方法将反射对象还原为接口类型的值。
 
            
            
              Go
              
              
            
          
          	o := reflect.ValueOf("hello,world") // 获取对象的值,即hello  world
	original := o.Interface().(string)   // 还原为原始对象,即"hello,world"的字符串
        - 可修改性条件: 只有可寻址的反射对象(如指针指向的值)才能被修改。
 
            
            
              Go
              
              
            
          
          x := 10
v := reflect.ValueOf(&x).Elem()
v.SetInt(20) // 修改 x 的值为 20
        
无论是TypeOf还是ValueOf,都可以使用kind方法,获取对象所属的数据类型。
            
            
              Go
              
              
            
          
          o := reflect.ValueOf("hello,world") // 获取对象的值,即hello  world
fmt.Println(o.Kind())          // 输出 string
        2.结构体的标签(Tags)与序列化🌊
标签是附加到字段的元数据,可通过反射读取,常用于序列化。下面介绍三种常用的标签json,xml 与**binding。**数据库标签Gorm等,以后做到具体项目再详细说明。
2.1 json序列化与反序列化🌸
            
            
              Go
              
              
            
          
          package main
import (
	"fmt"
	"github.com/goccy/go-json"
)
func main() {
	type User struct {
		ID       int    `json:"id"`
		Name     string `json:"username"`
		Password string `json:"pwd"` // 添加标签
	}
	user := User{ID: 1, Name: "Alice", Password: "secret"}
	// 序列化为JSON
	data, _ := json.Marshal(user)     // 也可用 err 代替 _ 接受Marshal返回的错误信息
	fmt.Println(string(data)) // 输出 {"id":1,"username":"Alice","pwd":"secret"}
}
        通过上述代码示例可知,使用Marshal函数,将结构体数据进行JSON序列化,以字符串string形式输出数据,json结构中的字段,即为结构体定义过程的个字段对应的标签名,而非结构体本身的字段名。
- 空值处理 :通过在标签中使用 omitempty,可以在进行 JSON 序列化时忽略空值字段。例如:
 
            
            
              Go
              
              
            
          
          package main
import (
	"fmt"
	"github.com/goccy/go-json"
	"reflect"
)
type Person struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Email string `json:"email,omitempty"`    // omitempty表示可忽略该字段内容
}
func main() {
	p := Person{Name: "John"}
	x, _ := json.Marshal(p)
	fmt.Println(string(x))  // 输出 {"name":"John","age":0}
}
        当初始数据omitempty对应字段为空值时,进行json序列化,则会无视该字段。
- 完全忽视:使用"-",可以完全忽略该字段进行 JSON 序列化和反序列化
 
            
            
              Go
              
              
            
          
          package main
import (
	"fmt"
	"github.com/goccy/go-json"
	"reflect"
)
type Person struct {
	Name  string `json:"-"`             // 完全忽略该字段
	Age   int    `json:"age"`
	Email string `json:"email,omitempty"`
}
func main() {
	p := Person{Name: "John", Age: 25}  // 即使结构体数据对应字段有内容,但仍完全忽略
	x, _ := json.Marshal(p)
	fmt.Println(string(x)) // 输出 {"age":25}
}
        使用"-"对应的字段,即使结构体数据有对应的存在数据,但在序列化过程中,仍然会被忽略,不进行序列化过程。此时该字段如同完全不存在一样。
下面介绍json的反序列化,即将一个json数据转为对应的结构体数据:
            
            
              Go
              
              
            
          
          package main
import (
	"fmt"
	"github.com/goccy/go-json"
	"reflect"
)
type Person struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Email string `json:"email,omitempty"`
}
func main() {
	jsonStr := `{"name":"Bob","Age":20}` // 注意 Email 未提供(omitempty 生效)
	var user Person
	err := json.Unmarshal([]byte(jsonStr), &user) // 反序列化到 user 对象
	if err != nil {
		panic(err)
	}
	fmt.Printf("%+v\n", user) //  输出{Name:Bob Age:20 Email:}
}
        使用Unmarshal函数对json的字符串序列进行反序列化,变为对应的结构体数据。
2.2 xml序列化与反序列化🌸
同json格式一致,具体可参照下面的例子:
- xml序列化:
 
Gopackage main import ( "encoding/xml" "fmt" ) type Book struct { Title string `xml:"title"` Author string `xml:"author"` } func main() { book := Book{Title: "Go in Action", Author: "Brian Ketelsen"} data, _ := xml.MarshalIndent(book, "xx", "yy") // 格式化输出 fmt.Println(string(data)) } // 输出: //xx<Book> //xxyy<title>Go in Action</title> //xxyy<author>Brian Ketelsen</author> //xx</Book>
- xml反序列化:
 
Gofunc main() { xmlStr := ` <Book> <title>The Go Programming Language</title> <author>Alan Donovan</author> </Book>` var book Book err := xml.Unmarshal([]byte(xmlStr), &book) if err != nil { panic(err) } fmt.Printf("%+v\n", book) // {Title:The Go Programming Language Author:Alan Donovan} }
2.3 binding序列化🌸
在 Go 语言中,结构体的 binding 标签用于指定结构体字段在进行数据绑定(如表单数据绑定或请求体绑定)时的规则和验证。用于gin验证器。
            
            
              Go
              
              
            
          
          type UserInfo struct{
	UserName string   `json:"username" binding:"required" msg"用户名不能	为空"`
	PassWord string `json:"password" binding:"min=3,max=6" msg"密码长度不能小于3大于6"`
	Email    string `json:"email" binding:"email" msg"邮箱地址格式不正确"`
}
        常用的binding控制字段如下表所示:
| 标签字段 | 说明 | 示例 | 
|---|---|---|
required | 
字段必须存在且非零值(非空字符串、非零数值、非nil指针等)。 | 
binding:"required" | 
omitempty | 
如果字段为零值,则跳过验证(需与 required 配合使用时有特殊行为)。 | 
binding:"omitempty,min=1" | 
min | 
数值的最小值,或字符串/数组的最小长度。 | binding:"min=5" | 
max | 
数值的最大值,或字符串/数组的最大长度。 | binding:"max=100" | 
len | 
字符串/数组的精确长度。 | binding:"len=11" | 
eq | 
字段值必须等于指定值(支持数值、字符串等)。 | binding:"eq=42" | 
ne | 
字段值必须不等于指定值。 | binding:"ne=0" | 
oneof | 
字段值必须在指定的枚举列表中(多个值用空格分隔)。 | binding:"oneof=red green blue" | 
gt | 
数值大于指定值(greater than)。 | 
binding:"gt=0" | 
gte | 
数值大于或等于指定值(greater than or equal)。 | 
binding:"gte=1" | 
lt | 
数值小于指定值(less than)。 | 
binding:"lt=100" | 
lte | 
数值小于或等于指定值(less than or equal)。 | 
binding:"lte=99" | 
eqfield | 
字段值必须等于另一个字段的值。 | binding:"eqfield=ConfirmPassword" | 
nefield | 
字段值必须不等于另一个字段的值。 | binding:"nefield=OldPassword" | 
alpha | 
字段值必须是字母字符(a-Z)。 | binding:"alpha" | 
alphanum | 
字段值必须是字母或数字字符(a-Z0-9)。 | binding:"alphanum" | 
numeric | 
字段值必须是数字字符(0-9)。 | binding:"numeric" | 
email | 
字段值必须是有效的邮箱格式。 | binding:"email" | 
url | 
字段值必须是有效的 URL。 | binding:"url" | 
datetime | 
字段值必须符合指定的日期时间格式(默认 2006-01-02)。 | 
binding:"datetime=2006-01-02" | 
contains | 
字段值必须包含指定子字符串。 | binding:"contains=example" | 
excludes | 
字段值必须不包含指定子字符串。 | binding:"excludes=admin" | 
startswith | 
字段值必须以指定字符串开头。 | binding:"startswith=prefix_" | 
endswith | 
字段值必须以指定字符串结尾。 | binding:"endswith=.com" | 
后续将会结合具体项目,进一步说明在项目当中的使用。
2.4 嵌套结构与标签组合🌸
对于嵌套的结构体,标签一样可以是嵌套的:
            
            
              Go
              
              
            
          
          package main
import (
	"fmt"
	"github.com/goccy/go-json"
)
type Person struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	email string `json:"email,omitempty"`
}
type Book struct {
	Title  string `json:"title"`
	Author Person `json:"author"`
}
func main() {
	book := Book{Title: "三体", Author: Person{Name: "刘慈欣", Age: 25}}
	jvalue, _ := json.Marshal(book)
	fmt.Println(string(jvalue))     
}
// 输出 {"title":"三体","author":{"name":"刘慈欣","age":25}}
        对于一个结构体,标签可以多个,组合使用:
            
            
              Go
              
              
            
          
          type Address struct {
    City  string `json:"city" binding:"required"`
    State string `json:"state" binding:"required,len=2"`
}
type User struct {
    Name    string  `json:"name" binding:"required"`
    Address Address `json:"address"`
}
        3.结构体与反射 🌊
| 维度 | reflect.TypeOf | 
reflect.ValueOf | 
|---|---|---|
| 核心作用 | 描述类型信息(如名称、方法、结构体字段)。 | 操作值信息(如获取值、修改值、调用方法)。 | 
| 修改能力 | 仅提供只读信息。 | 可通过 Set 方法修改值(需可寻址)。 | 
| 方法类型 | 聚焦类型元数据(如接口实现、方法列表)。 | 聚焦值操作(如类型转换、动态调用函数)。 | 
| 典型应用 | 类型检查、结构体分析、生成代码。 | 动态修改变量、序列化、依赖注入。 | 
常用的方法如下所示:

🌹公有方法-示例代码:
            
            
              Go
              
              
            
          
          package main
import (
	"fmt"
	"reflect"
)
type Person struct {
	Name  string `json:"name"`
	Age   int    `json:"age" binding:"required,min=20"`
	Email string `json:"email,omitempty"`
}
func main() {
	p := Person{Name: "John", Age: 25}
	v := reflect.ValueOf(p) // 获取值
	k := reflect.TypeOf(p)  // 获取类型
	num := k.NumField()     // 获取对应结构体当中的字段数
	fmt.Println("获取值:", v)
	fmt.Println("获取类型:", k)
	fmt.Println("获取对应结构体当中的字段数量:", num)
	fmt.Println("TypeOf的Field:")
	for i := 0; i < num; i++ {
		field := k.Field(i) // 按顺序获取各个字段对应的值
		fmt.Println(field, field.Type, field.Tag.Get("json"))
	}
	fmt.Println("ValueOf的Field:")
	for i := 0; i < num; i++ {
		field := v.Field(i) // 按顺序获取各个字段对应的值
		fmt.Println(field)
	}
}
        🌹Type独有方法示例
            
            
              Go
              
              
            
          
          // 判断是否实现某接口
var errType = reflect.TypeOf((*error)(nil)).Elem()
t := reflect.TypeOf(errors.New("test"))
fmt.Println(t.Implements(errType)) // true
// 获取指针指向的类型
ptr := reflect.TypeOf(&User{})
fmt.Println(ptr.Elem().Name()) // "User"
        🌹Value独有方法示例
            
            
              Go
              
              
            
          
          // 修改值(需可寻址)
x := 10
v := reflect.ValueOf(&x).Elem()
v.SetInt(20)
fmt.Println(x) // 20
// 动态调用函数
func Add(a, b int) int { return a + b }
v := reflect.ValueOf(Add)
result := v.Call([]reflect.Value{reflect.ValueOf(3), reflect.ValueOf(5)})
fmt.Println(result[0].Int()) // 8