Golang结构体中Tag的使用

Golang中可以为结构体的字段添加tag,这类似于Java中为类的属性添加的注解,Golang本身的encoding/json包解析json使用了tag,一些开源的ORM框架,也广泛使用了tag,那么,我们如何通过代码自己实现tag的解析,从而简化结构体字段的使用方式呢?下面看一个例子。

假设有一个Person结构体定义如下

type Person struct {
	Name        string `label:"Person Name: "  uppercase:"true"`
	Age         int    `label:"Age is: "`
	Sex         string `label:"Sex is: "`
	Description string
}

有四个字段,字段后面的使用`...`引用的部分就是tag,我们希望使用一个名为lable的tag来定义打印时候的标题,默认使用字段名称加冒号作为label。如果是字符串类型的字段,通过名称为uppercase的tag控制是否显示字符串的大写形式,默认按照小写。例如有一个Person结构体变量为

{Tom 29 Male Cool}

按照上面Person中tag的使用,打印应该为

Person Name: TOM

Age is: 29

Sex is: male

Description: cool

Golang解析标签主要通过反射实现,下面就看看我们如何实现上面的功能:

package main

import (
	"fmt"
	"reflect"
	"strings"
)

type Person struct {
	Name        string `label:"Person Name: " uppercase:"true"`
	Age         int    `label:"Age is: "`
	Sex         string `label:"Sex is: "`
	Description string
}

// 按照tag打印结构体
func PrintUseTag(ptr interface{}) error {

	// 获取入参的类型
	t := reflect.TypeOf(ptr)

	// 入参类型校验
	if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
		return fmt.Errorf("参数应该为结构体指针")
	}

	// 取指针指向的结构体变量
	v := reflect.ValueOf(ptr).Elem()

	// 解析字段
	for i := 0; i < v.NumField(); i++ {

		// 取tag
		fieldInfo := v.Type().Field(i)
		tag := fieldInfo.Tag

		// 解析label tag
		label := tag.Get("label")
		if label == "" {
			label = fieldInfo.Name + ": "
		}

		// 解析uppercase tag
		value := fmt.Sprintf("%v", v.Field(i))
		if fieldInfo.Type.Kind() == reflect.String {
			uppercase := tag.Get("uppercase")
			if uppercase == "true" {
				value = strings.ToUpper(value)
			} else {
				value = strings.ToLower(value)
			}
		}

		fmt.Println(label + value)
	}

	return nil
}

func main() {
	person := Person{
		Name:        "Tom",
		Age:         29,
		Sex:         "Male",
		Description: "Cool",
	}

	PrintUseTag(&person)
}

上面代码,main函数创建了一个名为person的Person结构体变量,然后调用PrintUseTag函数进行打印,所以主要的逻辑我们要看PrintUseTag函数。该函数通过反射,获取tag,然后按照不同的情况解析tag并执行不同的操作,只有字段类型为字符串时候,我们才去判断uppercase tag是否使用,之后打印每一个字段的label和value组成的字符串。value不一定都是字符串类型,所以我们借助于fmt包的Sprintf函数的%v,将其他类型转化为字符串表述。

掌握了tag的使用,我们可以使用Golang定义自己的很多工具tag,减少代码量,尤其在定义一些框架时作用更加明显。

最近又做了个升级,修改了label的形式,同时支持结构内嵌套结构

package main

import (
	"fmt"
	"reflect"
	"runtime/debug"
	"strings"
)

type Person struct {
	Name          string `print:"label:Person Name,uppercase"`
	Age           *int   `print:"label:Age is"`
	Sex           string `print:"uppercase"`
	Description   string
	FirstAddress  *PersonAddress
	SecondAddress PersonAddress `print:"label:Other address is"`
}

type PersonAddress struct {
	Address string `print:"uppercase,label:Address is"`
	Phone   string `print:"label:Phone is"`
}

func main() {
	age := 12

	person := &Person{
		Name:        "test",
		Age:         &age,
		Sex:         "男",
		Description: "123456",
		FirstAddress: &PersonAddress{
			Address: "address1",
			Phone:   "phone1",
		},
		SecondAddress: PersonAddress{
			Address: "address2",
			Phone:   "phone2",
		},
	}

	PrintUseTag(person)
}

func PrintUseTag(ptr interface{}) {
	// 获取入参的类型
	t := reflect.TypeOf(ptr)

	// 入参类型校验
	if t.Kind() != reflect.Struct && (t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct) {
		fmt.Println("参数应该为结构体指针")
		debug.PrintStack()
		return
	}

	// 取指针指向的结构体变量
	ptrValue := reflect.ValueOf(ptr)
	if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
		ptrValue = reflect.ValueOf(ptr).Elem()
	}

	// 解析字段
	for i := 0; i < ptrValue.NumField(); i++ {
		// 取tag
		fieldInfo := ptrValue.Type().Field(i)
		tag := fieldInfo.Tag

		// 解析print tag
		printTagMap := make(map[string]string)

		printTags := strings.Split(tag.Get("print"), ",")
		if len(printTags) == 0 {
			printTagMap["label"] = fieldInfo.Name + ": "
		}

		for _, printTag := range printTags {
			tagKeyAndValue := strings.SplitN(printTag, ":", 2)
			switch tagKeyAndValue[0] {
			case "label":
				printTagMap["label"] = tagKeyAndValue[1] + ": "
			case "uppercase":
				printTagMap["uppercase"] = "true"
			}
		}

		if printTagMap["label"] == "" {
			printTagMap["label"] = fieldInfo.Name + ": "
		}

		var value string

		field := ptrValue.Field(i)
		if field.Kind() == reflect.Struct {
			fmt.Println(printTagMap["label"])
			PrintUseTag(field.Interface())
			continue
		}

		if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
			fmt.Println(printTagMap["label"])
			PrintUseTag(field.Interface())
			continue
		}

		value = fmt.Sprintf("%v", field)

		// 解析uppercase tag
		if fieldInfo.Type.Kind() == reflect.String {
			uppercase := printTagMap["uppercase"]
			if uppercase == "true" {
				value = strings.ToUpper(value)
			} else {
				value = strings.ToLower(value)
			}
		}

		fmt.Println(printTagMap["label"] + value)
	}
}

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值