目录
[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