From f28f3cbe2b10e538fd79ac9aa6cd271b5a17acbd Mon Sep 17 00:00:00 2001 From: Shulhan Date: Wed, 1 Jun 2022 22:11:10 +0700 Subject: lib/reflect: add function Tag to simplify lookup on struct's field tag Given a StructField and the name of tag, return the tag's value and options inside the tag. The options is any string after tag's value, separated by comma. For example, given the following field definition F `tag:"name,opt1, opt2"` It will return (name, [opt1 opt2], true). If the field is exported but does not have tag, it will return the field name (as is without converting to lower case) in val with hasTag is false: (Name, nil, false). If the field is un-exported it will return empty val with hasTag is false ("", nil, false). --- lib/reflect/reflect.go | 51 +++++++++++++++++++++++++++++++++++++ lib/reflect/reflect_example_test.go | 33 ++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/lib/reflect/reflect.go b/lib/reflect/reflect.go index b8dba25d..4b22d3d9 100644 --- a/lib/reflect/reflect.go +++ b/lib/reflect/reflect.go @@ -8,6 +8,7 @@ package reflect import ( "fmt" "reflect" + "strings" "unsafe" ) @@ -59,6 +60,56 @@ func IsNil(v interface{}) bool { return v == nil } +// Tag simplify lookup on struct's field tag. +// +// Given a StructField and the name of tag, return the tag's value and +// options inside the tag. +// The options is any string after tag's value, separated by comma. +// For example, given the following field definition +// +// F `tag:"name,opt1, opt2"` +// +// It will return (name, [opt1 opt2], true). +// +// If the field is exported but does not have tag, it will return the field +// name (as is without converting to lower case) in val with hasTag is false: +// (Name, nil, false). +// +// If the field is unexported it will return empty val with hasTag is false +// ("", nil, false). +func Tag(field reflect.StructField, tag string) (val string, opts []string, hasTag bool) { + if len(field.PkgPath) != 0 { + // field is unexported. + return "", nil, false + } + + var ( + x int + ) + + val, hasTag = field.Tag.Lookup(tag) + if !hasTag { + // Tag not defined, so we use field name as key. + val = field.Name + } else { + opts = strings.Split(val, ",") + for x, val = range opts { + opts[x] = strings.TrimSpace(val) + } + + val = opts[0] + opts = opts[1:] + if len(val) == 0 { + // Tag is empty, use field name as key and + // mark it as not OK. + val = field.Name + hasTag = false + } + } + + return val, opts, hasTag +} + // doEqual compare two kind of objects and return nils if both are equal. // // If its not equal, it will return the interface{} value of v1 and v2 and diff --git a/lib/reflect/reflect_example_test.go b/lib/reflect/reflect_example_test.go index 58e6d1cc..f0919a6f 100644 --- a/lib/reflect/reflect_example_test.go +++ b/lib/reflect/reflect_example_test.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "reflect" ) type F func() @@ -70,3 +71,35 @@ func ExampleIsNil() { // *errors.errorString: v == nil is false, IsNil() is false // : v == nil is true, IsNil() is true } + +func ExampleTag() { + type T struct { + F1 int `atag:" f1 , opt1 , opt2 ,"` + F2 int `atag:", opt1"` + F3 int + f4 int + } + + var ( + t T + vtype reflect.Type + field reflect.StructField + val string + opts []string + x int + hasTag bool + ) + + vtype = reflect.TypeOf(t) + + for x = 0; x < vtype.NumField(); x++ { + field = vtype.Field(x) + val, opts, hasTag = Tag(field, "atag") + fmt.Printf("%q %v %v\n", val, opts, hasTag) + } + //Output: + //"f1" [opt1 opt2 ] true + //"F2" [opt1] false + //"F3" [] false + //"" [] false +} -- cgit v1.3