盘点一下结构体标签在Go中的应用
掌握了Go
语言的朋友们应该都知道,在Go
的结构体类型声明里面,字段声明后可以跟一个可选的字符串标签。
type User struct {
Name string `json:"name"`
}
上面是一个标准的例子,Name
字段声明中指定了标签json:"name" xml:"name"
,这个标签值看着有点类似Java
程序里给类属性加的注解。
那么这些结构体标签有什么用途呢,我们随便写管用吗?我们平时工作中常用的结构体标签有哪些呢?我们能不能自己定义结构体标签?今天就带大家掰扯清楚这些问题!
结构体标签
Go语言允许我们通过结构体字段标签给一个字段附加可以被反射获取的”元信息“,正好我们上篇文章实战演示Go反射的使用方法和应用场景中讲了Go语言反射使用方法相关的内容,对反射不清楚的可以先去再复习一下。
通常情况下,结构体标签被用于提供结构体字段如何被编码为或者解码自另外一种格式的转换信息(或者是以何种形式被保存至/获取自数据库)。不过,你也可以用它存储任何你想要设置的”元信息“,供其他包或者自己使用。
使用规范
结构体标签在使用上通常是遵守下面三个规范。
结构体标签字符串的值是一个由空格分隔的 key:"value" 对列表,例如:
type User struct {
Name string `json:"name" xml:"name"`
}
键,通常表示后面跟的“值”是被哪个包使用的,例如json
这个键会被encoding/json
包处理使用。如果要在“键”对应的“值”中传递多个信息,通常通过用逗号(',')分隔来指定,例如
Name string `json:"name,omitempty"`
按照惯例,如果一个字段的结构体标签里某个键的“值”被设置成了的破折号 ('-'),那么就意味着告诉处理该结构体标签键值的进程排除该字段。例如,把一个字段的标签设置成下面这样
Name string `json:"-"`
就以为进行JSON编码/解码时忽略Name
这个字段。
怎么获取到结构体标签
从一开始我们就说结构体标签是给反射准备的,那么怎么在Go
程序里用反射获得到字段的结构体标签呢?看了我们上一篇文章的同学,应该会知道,结构体字段类型相关的信息,在反射的世界里使用reflect.StructFiled
这个类型表示的。
type StructField struct {
Name string
Type Type // field type
Tag StructTag // field tag string
......
}
如上所示,其中包含的Tag
字段即代表了字段声明中的结构体标签信息。让我们通过自定义结构体标签的例子来演示一下怎么使用它在反射里读取到标签里的信息。
用反射获取到自定义的结构体标签
使用反射reflect
包访问结构体字段的标签值,我们需要先获取到结构体的类型信息Type
,然后使用Type.Field(i int)
或 Type.FieldByName(name string)
,方法查询字段信息,这两个方法都会返回一个StructField
类型的值,上面我们也说了它在反射的世界里用于描述一个结构体字段;而StructField.Tag
是一个StructTag
类型的值,它描述了字段的标签。
上面我们谈到了结构体标签的使用规范,如果遵循规范给字段设置了标签后,就可以使用StructTag
的Get
方法解析标签的值并返回你指定的键的“值”。
func (tag StructTag) Get(key string) string
为了方便判断一个给定的key
是否存在与标签中,StructTag
还提供了一个Lookup
方法
func (tag StructTag) Lookup(key string) (value string, ok bool)
跟Get
方法不同的是,Lookup
会通过返回的ok
值告知给定key
是否存在与标签中。
下面通过一个例子,演示下获取我们自定义标签的过程。
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `mytag:"MyName"`
Email string `mytag:"MyEmail"`
}
func main() {
u := User{"Bob", "bob@mycompany.com"}
t := reflect.TypeOf(u)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field: User.%s\n", field.Name)
fmt.Printf("\tWhole tag value : %s\n", field.Tag)
fmt.Printf("\tValue of 'mytag': %s\n", field.Tag.Get("mytag"))
}
}
上面的程序会输出
Field: User.Name
Whole tag value : mytag:"MyName"
Value of 'mytag': MyName
Field: User.Email
Whole tag value : mytag:"MyEmail"
Value of 'mytag': MyEmail
常用的结构体标签键
常用的结构体标签Key,指的是那些被一些常用的开源包声明使用的结构体标签键。在这里总结了一些,都是一些我们平时会用到的包,它们是:
json
: 由encoding/json
包使用,详见json.Marshal()
的使用方法和实现逻辑。xml
: 由encoding/xml
包使用,详见xml.Marshal()
。bson
: 由gobson
包,和mongo-go
包使用。protobuf
: 由github.com/golang/protobuf/proto
使用,在包文档中有详细说明。yaml
: 由gopkg.in/yaml.v2
包使用,详见yaml.Marshal()
。gorm
: 由gorm.io/gorm
包使用,示例可以在GORM的文档中找到。
当然这里列的就是最常用的几个库他们提供给我们使用的结构体标签,欢迎大伙踊跃留言,补充一些自己平时用过的库提供给开发者使用的结构体标签。
总结
这篇文章算是我们上一篇讲Go反射的一个实践方向的延伸介绍,如果你也想在自己的包里提供一些结构体标签键,让自己的包更易用些,除了看咱们这篇文章外,还可以去看看上面咱们介绍的几个类库,看它们的源码里是怎么应用的,现学现用!
json中omitempty字段的使用 原创
总结
1. omitempty是省略的意思
2. json中字段若有omitempty标记,则这个字段为空时,json序列化为string时不会包含该字段
3. json中字段若没有omitempty标记,则这个字段为空时,json序列化为string时会包含该字段
看代码
-
package main
-
import (
-
"encoding/json"
-
"fmt"
-
)
-
// 学生信息
-
type Student struct {
-
Id int `json:"id"`
-
Name string `json:"name,omitempty"`
-
}
-
func main() {
-
// 测试第一个(Name字段赋值,序列化后这个字段是存在的)
-
s1 := Student{
-
Id:1,
-
Name:"张三",
-
}
-
data1, _ := json.Marshal(s1)
-
fmt.Printf("%s\n", data1)
-
-
// 测试第二个(Id字段不赋值,序列化后Id字段仍然存在;Name字段不赋值,序列化后这个字段就不存在了)
-
s2 := Student{
-
//Id:2,
-
//Name:"李四",
-
}
-
data2, _ := json.Marshal(s2)
-
fmt.Printf("%s\n", data2)
-
}
实验如下