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
相关推荐
Tttian6221 小时前
Python办公自动化(3)对Excel的操作
开发语言·python·excel
独好紫罗兰2 小时前
洛谷题单2-P5713 【深基3.例5】洛谷团队系统-python-流程图重构
开发语言·python·算法
zhuyixiangyyds3 小时前
day21和day22学习Pandas库
笔记·学习·pandas
每次的天空3 小时前
Android学习总结之算法篇四(字符串)
android·学习·算法
闪电麦坤953 小时前
C#:base 关键字
开发语言·c#
Mason Lin3 小时前
2025年3月29日(matlab -ss -lti)
开发语言·matlab
DREAM.ZL3 小时前
基于python的电影数据分析及可视化系统
开发语言·python·数据分析
難釋懷4 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript
jingjingjing11114 小时前
笔记:docker安装(ubuntu 20.04)
笔记·docker·容器
海姐软件测试4 小时前
Postman参数化设置如何设置?
开发语言·jmeter