Go语言学习(15)结构体标签与反射机制

目录

📝前言

1.反射的基础🌊

🏆反射三大定律:

2.结构体的标签(Tags)与序列化🌊

[2.1 json序列化与反序列化🌸](#2.1 json序列化与反序列化🌸)

[2.2 xml序列化与反序列化🌸](#2.2 xml序列化与反序列化🌸)

[2.3 binding序列化🌸](#2.3 binding序列化🌸)

[2.4 嵌套结构与标签组合🌸](#2.4 嵌套结构与标签组合🌸)

[3.结构体与反射 🌊](#3.结构体与反射 🌊)

🌹公有方法-示例代码:

🌹Type独有方法示例

🌹Value独有方法示例


📝前言

本文将主要介绍结构体标签(Tags)与基于reflect包的反射机制,这两个内容在网络编程中比较常用到。

1.反射的基础🌊

反射的核心是reflect.Typereflect.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序列化:
Go 复制代码
package 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反序列化:
Go 复制代码
func 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
相关推荐
biubiubiu070621 小时前
Ubuntu学习笔记
笔记·学习·ubuntu
lsx2024061 天前
Ruby CGI Cookie 使用指南
开发语言
musenh1 天前
javascript学习
开发语言·javascript·学习
凉、介1 天前
ARM 总线技术 —— APB
arm开发·笔记·学习
爱奥尼欧1 天前
【Linux笔记】网络部分——网络层IP协议
linux·网络·笔记
沐知全栈开发1 天前
SVG 参考手册
开发语言
Summer_Uncle1 天前
【C++学习】对象特性--继承
开发语言·c++·学习
西贝爱学习1 天前
【QT】安装包
开发语言·qt
岁忧1 天前
Go channel 的核心概念、操作语义、设计模式和实践要点
网络·设计模式·golang
徐凤年lll1 天前
python 初学2
开发语言·python