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) } }
- 本文固定链接: https://phpmianshi.com/?id=668
- 转载请注明: golang中国 于 PHP面试网 发表
《本文》有 0 条评论