上一篇文章咱们学习了如何用 reflect 查看一个参数的类型。这一篇文章,咱们取得了一个构造体类型,那么咱们须要探索构造体外部的构造以及其对应的值。
构造体成员迭代
上一篇文章,咱们的 marshal 函数目前是长这个样子:
func marshalToValues(in interface{}) (kv url.Values, err error) {
v, err := validateMarshalParam(in)
if err != nil {
return nil, err
}
// ......
}
到这里,咱们拿到了一个 struct 的 reflect.Value 变量。接下来,咱们再增加几行代码,变成这个样子:
func marshalToValues(in interface{}) (kv url.Values, err error) {
v, err := validateMarshalParam(in)
if err != nil {
return nil, err
}
t := v.Type()
numField := t.NumField()
kv = url.Values{}
// 迭代每一个字段
for i := 0; i < numField; i++ {
fv := v.Field(i) // field value
ft := t.Field(i) // field type
// ......
}
return kv, nil
}
treflect.TypeNumField()
成员解析流程
fv := v.Field(i)ft := t.Field(i)reflect.Valuereflect.StructField
对于一个构造体成员,除了字段碑身类型之外,咱们还要对其其余属性进行查看,这须要用到 fv 和 ft 变量的几个参数,如下文所示:
匿名成员
Go 的构造体中,反对匿名成员。针对匿名成员的解决,有好几个须要思考的点。此处咱们先略过,后文会再专门阐明,因而代码如下:
func marshalToValues(in interface{}) (kv url.Values, err error) {
// ......
// 迭代每一个字段
for i := 0; i < numField; i++ {
fv := v.Field(i) // field value
ft := t.Field(i) // field type
if ft.Anonymous { // 是否匿名成员
// TODO: 后文再解决
continue
}
// ......
}
return kv, nil
}
不可导出成员
Go 的构造体中,共有(可导出)成员是大写字母结尾的,而公有(不可导出)成员是小写字母结尾的。依照 Go 的常规,在进行 marshal / unmarshal 操作时,公有成员是不解决的,因而这些成员,咱们该当过滤掉不解决。
然而有一种状况是例外的:匿名成员自身也有可能是不可导出的,这须要辨别解决。所以咱们把匿名成员的解决逻辑放在了后面。因而此时的代码改写为如下所示:
func marshalToValues(in interface{}) (kv url.Values, err error) {
// ......
// 迭代每一个字段
for i := 0; i < numField; i++ {
fv := v.Field(i) // field value
ft := t.Field(i) // field type
if ft.Anonymous { // 是否匿名成员
// TODO: 后文再解决
continue
}
if !fv.CanInterface() { // 是否可导出,应用 fv 变量的 CanInterface 函数进行判断
continue
}
// ......
}
return kv, nil
}
Go tag 解析
tag
type Pet struct {
OwnerID string `url:"owner_id,omitempty" json:"ownerID"`
Name string `url:",omitempty"`
Sex int
}
ownerIDOwnerIDomitempty
Name
*reflect.StructField
-
如果指定的 tag 配置非空,则分两种状况:
- 都好之前有内容,那么逗号之前的数据就是 key 名称
- 逗号之前没有内容,此时用字段的名称作为 tag
- 如果指定的 tag 配置不存在,则以字段的名称作为 tag
- 反对获取其余参数
type tags []string
func readTag(ft *reflect.StructField, tag string) tags {
tg := ft.Tag.Get(tag)
// 如果 tag 配置非空,则返回
if tg != "" {
res := strings.Split(tg, ",")
if res[0] != "" {
return res
}
return append(tags{ft.Name}, res[1:]...)
}
// 如果 tag 配置为空,则返回字段名
return tags{ft.Name}
}
// Name 示意以后 tag 所定义的第一个字段,这个字段必须是名称
func (tg tags) Name() string {
return tg[0]
}
// Has 判断以后 tag 是否配置了某些额定参数值,比方 omitempty
func (tg tags) Has(opt string) bool {
for i := 1; i < len(tg); i++ {
t := tg[i]
if t == opt {
return true
}
}
return false
}
Pet
-
func marshalToValues(in interface{}) (kv url.Values, err error) {
// ......
// 迭代每一个字段
for i := 0; i < numField; i++ {
fv := v.Field(i) // field value
ft := t.Field(i) // field type
if ft.Anonymous { // 是否匿名成员
// TODO: 后文再解决
continue
}
if !fv.CanInterface() { // 是否可导出,应用 fv 变量的 CanInterface 函数进行判断
continue
}
tg := readTag(&ft, "url")
if tg.Name() == "-" { // - 示意疏忽以后字段
continue
}
// ......
}
return kv, nil
}
构造体成员值读取
通过了后面的过滤之后,咱们到这一步,曾经能够取得每个须要解决的、非法的构造体字段信息了,接下来就是获取每一个构造体成员的值。
fvreflect.Value
reflect.Kind
- 字符串
- 整型
- 浮点型
- 布尔型
至于其余类型则比较复杂,咱们再进一步在后文阐明。
多说无益,这一小段代码并不长,如下所示:
func readFieldVal(v *reflect.Value, tag tags) (s string, ok bool) {
switch v.Type().Kind() {
default:
return "", false // 不反对的变量类型,间接返回
case reflect.String:
return v.String(), true
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
return strconv.FormatInt(v.Int(), 10), true
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
return strconv.FormatUint(v.Uint(), 10), true
case reflect.Bool:
return fmt.Sprintf("%v", v.Bool()), true
case reflect.Float64, reflect.Float32:
return strconv.FormatFloat(v.Float(), 'f', -1, 64), true
}
}
代码中展现了针对各种类型地取值函数:
v.String()v.Uint()uint64v.Int()int64reflect.Boolv.Bool()v.Float()float64
于是,很快啊,咱们的迭代函数的主体,就实现了:
func marshalToValues(in interface{}) (kv url.Values, err error) {
v, err := validateMarshalParam(in)
if err != nil {
return nil, err
}
t := v.Type()
numField := t.NumField() // 构造体下所有字段的数量
kv = url.Values{}
// 迭代每一个字段
for i := 0; i < numField; i++ {
fv := v.Field(i) // field value
ft := t.Field(i) // field type
if ft.Anonymous {
// TODO: 后文再解决
continue
}
if !fv.CanInterface() {
continue
}
tg := readTag(&ft, "url")
if tg.Name() == "-" {
continue
}
str, ok := readFieldVal(&fv, tg)
if !ok {
continue
}
if str == "" && tg.Has("omitempty") {
continue
}
// 写 KV 值
kv.Set(tg.Name(), str)
}
return kv, nil
}
验证
咱们写一个简略的 Go test 函数来验证一下:
func TestMarshal(t *testing.T) {
type Pet struct {
OwnerID string `url:"owner_id,omitempty" json:"ownerID"`
Name string `url:",omitempty"`
Sex int
}
p := Pet{
OwnerID: "tencent",
Name: "Penguin",
Sex: 1,
}
s, _ := Marshal(&p)
t.Log(string(s))
}
// 输入
// Name=Penguin&Sex=1&owner_id=tencent
能够看到,输入内容中正确地依照 tag 中的配置,将构造体中的字段序列化为了字节流。
下一步
OK,如果读者的需要中,仅仅须要序列化根本数据类型(字符串、布尔值、数字),那么到这里为止,marshal 函数就能够算是实现了。
然而读者是否还记得咱们在本文中留下了些 TODO 项,这就是咱们在下一篇文章中须要解决的性能了。本文的代码也能够在 Github 上找到,本阶段的代码对应 Commit b2db350。
其余文章举荐
map[string]interface{}
本文章采纳 常识共享署名-非商业性应用-雷同形式共享 4.0 国内许可协定 进行许可。
原文题目:《手把手教你用 reflect 包解析 Go 的构造体 – Step 2: 构造体成员遍历》
公布日期:2021-06-29
原文链接:https://cloud.tencent.com/developer/article/1336510。