01-数据类型之值类型之结构体

Tag 简介

Tag 用于标识结构体字段的额外属性,有点类似于注释。Tag 是结构体在编译阶段关联到成员的元信息字符串,在运行的时候通过反射的机制读取出来。标准库 reflect 包中提供了操作Tag的方法

结构体的字段的定义。在 reflect 包中,使用结构体 structField 表示结构体的一个字段

1
2
3
4
5
6
7
8
type StructField struct {
Name string
//字段名
Type Type
//字段类型
Tag StructTag //Tag 的类型为structTag,实际上它是一个string类型的别名
//Tag
}

Tag 的意义

Go 的反射特性可以动态地给结构体成员赋值,正是因为有 Tag,在赋值前可以使用 Tag 来决定赋值的动作。

比如,官方的 encoding/json 包可以将一个 JSON 数据“Unmarshal”进一个结构体,此过程中就使用了 Tag。该包定义了一些 Tag 规则,只要参考该规则设置 Tag 就可以将不同的JSON数据转换成结构体。

在 Go 中,命名都是推荐用驼峰方式,并且在首字母大小写有特殊的语法含义:包外无法引用。

但是由于经常需要和其它的系统进行数据交互,例如转成json格式,存储到mongodb啊等等。这个时候如果用属性名来作为键值可能不一定会符合项目要求。

而通过 Tag,我们可以在转换成其它格式的时候,使用其中定义的字段作为键值。

1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
UserId int `json:"user_id"`
UserName string `json:"user_name"`
}
func main() {
u := &User{UserId: 1, UserName: "张三"}
j, _ := json.Marshal(u)
fmt.Println(string(j))
}


//{"user_id":1,"user_name":"张三"}

Tag 约定

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔,具体的格式如下:

1
`key1:"value1" key2:"value2" key3:"value3"...`  // 键值对用空格分隔

Tag 本身是一个字符串,单从语义上讲,任意的字符串都是合法的。但它有一个约定的格式,那就是字符串由key:"value"组成。

  • key 必须是非空字符串,字符串不能包含控制字符、空格、引号、冒号;
  • value 以双引号标记的字符串。

注意: key 和 value 之间使用冒号分隔,冒号前后不能有空格,多个 key:"value" 之间由空格分开。

1
Kind string `json:"kind, omitempty" protobuf:"bytes,1, opt, name=kind"`

key 一般表示用途,比如 json 表示用于控制结构体类型与 JSON 格式数据之间的转换,protobuf 表示用于控制序列化和反序列化。value 一般表示控制指令,具体控制指令由不同的库指定。

获取 Tag 值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"reflect"
)

type Food struct {
Apple string `fruit:"apple"`
Tomato string `vegetable:"tomato"`
}

func main() {
t := reflect.TypeOf(Food{})
f, _ := t.FieldByName("Apple")
fmt.Println(f.Tag)

// Tag.Lookup
v, ok := f.Tag.Lookup("fruit")
fmt.Printf("%s, %t\n", v, ok)

// Tag.Get
v = f.Tag.Get("fruit")
fmt.Println(v)
}

运行结果

1
2
3
fruit:"apple"
apple, true
apple

一些常用的 Tag

  • json(JSON标签) :由encoding/json包使用,详细信息见json.Marshal()
  • xml:由encoding/xml包使用,详细信息见xml.Marshal()
  • bson(MongoDB标签):由gobson使用,详细信息在bson.Marshal();也由mongo-go驱动程序,在bson package doct中有详细说明
  • protobuf-github.com/golang/protobuf/proto使用者,文档中有详细说明
  • yaml:由gopkg.in/yaml.v2包使用,详细信息见yaml.Marshal()
  • db:被github.com/jmoiron/sqlx包使用;也被github.com/go-gorp/gorp包使用
  • orm(Beego标签):由github.com/astaxie/beego/orm包使用,详见Models-Beego ORM
  • gorm(GORM标签):使用gorm.io/gorm,示例可以在他们的文档中找到
  • valid:由github.com/asaskevich/govalidator包使用,示例可以在项目页面中找到
  • datastore:由appengine/datastore(Google App Engine平台、Datastore服务)使用,详情见属性
  • schema:用于github.com/gorilla/schema填充struct HTML表单值,在包文档中有详细说明
  • asn:由encoding/asn1包使用,详细信息在asn1.Marshal()asn1.Unmarshal()
  • csv:被github.com/gocarina/gocsv包使用
  • env:被github.com/caarlos0/env包使用
  • form(表单标签)
  • binding(表单验证标签)

json Tag

1
2
3
4
5
type Student struct {
ID int `json:"-"` // 该字段不进行序列化
Name string `json:name,omitempy` // 如果为类型零值或空值,序列化时忽略该字段
Age int `json:age,string` // 重新指定字段类型,支持string、number、boolen
}

https://studygolang.com/static/pkgdoc/pkg/encoding_json.htm

json 编码

1
2
3
4
5
type User struct {
ID int `json:"id"` // 编码后的字段名为 id
Name string // 编码后的字段名为 自定义成员名 Name
age int // 未导出字段不能编码
}

在 Go 语言中,如果一个结构体的字段名首字母是大写,那么它就是一个 “导出字段”(Exported Field),意味着它可以被外部的包访问和操作。反之,如果一个字段名首字母是小写,那么它就是一个 “未导出字段”(Unexported Field),只能在当前的包内部被访问和操作。

在进行 JSON 编码(encoding)或解码(decoding)时,只有结构体的导出字段才会被处理。


01-数据类型之值类型之结构体
https://flepeng.github.io/021-Go-31-Go-基础-01-数据类型之值类型之结构体之-Tag/
作者
Lepeng
发布于
2024年12月2日
许可协议