util/reflect: add new funcs

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2021-07-22 15:45:31 +03:00
parent a7e6d61b95
commit a839f75a2f
2 changed files with 110 additions and 11 deletions

View File

@ -7,7 +7,6 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
"time"
) )
// ErrInvalidParam specifies invalid url query params // ErrInvalidParam specifies invalid url query params
@ -15,11 +14,12 @@ var ErrInvalidParam = errors.New("invalid url query param provided")
var bracketSplitter = regexp.MustCompile(`\[|\]`) var bracketSplitter = regexp.MustCompile(`\[|\]`)
var timeKind = reflect.TypeOf(time.Time{}).Kind() //var timeKind = reflect.ValueOf(time.Time{}).Kind()
type StructField struct { type StructField struct {
Field reflect.StructField Field reflect.StructField
Value reflect.Value Value reflect.Value
Path string
} }
func StructFieldByTag(src interface{}, tkey string, tval string) (interface{}, error) { func StructFieldByTag(src interface{}, tkey string, tval string) (interface{}, error) {
@ -35,7 +35,7 @@ func StructFieldByTag(src interface{}, tkey string, tval string) (interface{}, e
for idx := 0; idx < typ.NumField(); idx++ { for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx) fld := typ.Field(idx)
val := sv.Field(idx) val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 { if len(fld.PkgPath) != 0 {
continue continue
} }
@ -66,6 +66,17 @@ func StructFieldByTag(src interface{}, tkey string, tval string) (interface{}, e
return nil, ErrNotFound return nil, ErrNotFound
} }
func StructFieldByPath(src interface{}, path string) (interface{}, error) {
var err error
for _, p := range strings.Split(path, ".") {
src, err = StructFieldByName(src, p)
if err != nil {
return nil, err
}
}
return src, err
}
func StructFieldByName(src interface{}, tkey string) (interface{}, error) { func StructFieldByName(src interface{}, tkey string) (interface{}, error) {
sv := reflect.ValueOf(src) sv := reflect.ValueOf(src)
if sv.Kind() == reflect.Ptr { if sv.Kind() == reflect.Ptr {
@ -79,7 +90,7 @@ func StructFieldByName(src interface{}, tkey string) (interface{}, error) {
for idx := 0; idx < typ.NumField(); idx++ { for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx) fld := typ.Field(idx)
val := sv.Field(idx) val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 { if len(fld.PkgPath) != 0 {
continue continue
} }
if fld.Name == tkey { if fld.Name == tkey {
@ -105,6 +116,19 @@ func StructFieldByName(src interface{}, tkey string) (interface{}, error) {
return nil, ErrNotFound return nil, ErrNotFound
} }
// StructFieldsMap returns map[string]interface{} or error
func StructFieldsMap(src interface{}) (map[string]interface{}, error) {
fields, err := StructFields(src)
if err != nil {
return nil, err
}
mp := make(map[string]interface{}, len(fields))
for _, field := range fields {
mp[field.Path] = field.Value.Interface()
}
return mp, nil
}
// StructFields returns slice of struct fields // StructFields returns slice of struct fields
func StructFields(src interface{}) ([]StructField, error) { func StructFields(src interface{}) ([]StructField, error) {
var fields []StructField var fields []StructField
@ -116,25 +140,29 @@ func StructFields(src interface{}) ([]StructField, error) {
if sv.Kind() != reflect.Struct { if sv.Kind() != reflect.Struct {
return nil, ErrInvalidStruct return nil, ErrInvalidStruct
} }
typ := sv.Type() typ := sv.Type()
for idx := 0; idx < typ.NumField(); idx++ { for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx) fld := typ.Field(idx)
val := sv.Field(idx) val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 { if !val.IsValid() || len(fld.PkgPath) != 0 {
continue continue
} }
switch val.Kind() { switch val.Kind() {
case timeKind: //case timeKind:
fields = append(fields, StructField{Field: fld, Value: val}) //fmt.Printf("GGG\n")
//fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name})
case reflect.Struct: case reflect.Struct:
infields, err := StructFields(val.Interface()) infields, err := StructFields(val.Interface())
if err != nil { if err != nil {
return nil, err return nil, err
} }
fields = append(fields, infields...) for _, infield := range infields {
infield.Path = fmt.Sprintf("%s.%s", fld.Name, infield.Path)
fields = append(fields, infield)
}
default: default:
fields = append(fields, StructField{Field: fld, Value: val}) fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name})
} }
} }
@ -194,7 +222,7 @@ func StructURLValues(src interface{}, pref string, tags []string) (url.Values, e
for idx := 0; idx < typ.NumField(); idx++ { for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx) fld := typ.Field(idx)
val := sv.Field(idx) val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 || !val.IsValid() { if len(fld.PkgPath) != 0 || !val.IsValid() {
continue continue
} }

View File

@ -2,9 +2,80 @@ package reflect
import ( import (
"net/url" "net/url"
"reflect"
rfl "reflect"
"testing" "testing"
) )
func TestStructFieldsMap(t *testing.T) {
type NestedStr struct {
BBB string
CCC int
}
type Str struct {
Name []string `json:"name" codec:"flatten"`
XXX string `json:"xxx"`
Nested NestedStr
}
val := &Str{Name: []string{"first", "second"}, XXX: "ttt", Nested: NestedStr{BBB: "ddd", CCC: 9}}
fields, err := StructFieldsMap(val)
if err != nil {
t.Fatal(err)
}
if v, ok := fields["Nested.BBB"]; !ok || v != "ddd" {
t.Fatalf("invalid field from %v", fields)
}
}
func TestStructFields(t *testing.T) {
type NestedStr struct {
BBB string
CCC int
}
type Str struct {
Name []string `json:"name" codec:"flatten"`
XXX string `json:"xxx"`
Nested NestedStr
}
val := &Str{Name: []string{"first", "second"}, XXX: "ttt", Nested: NestedStr{BBB: "ddd", CCC: 9}}
fields, err := StructFields(val)
if err != nil {
t.Fatal(err)
}
var ok bool
for _, field := range fields {
if field.Path == "Nested.CCC" {
ok = true
}
}
if !ok {
t.Fatalf("struct fields returns invalid path: %v", fields)
}
}
func TestStructByPath(t *testing.T) {
type NestedStr struct {
BBB string
CCC int
}
type Str struct {
Name []string `json:"name" codec:"flatten"`
XXX string `json:"xxx"`
Nested NestedStr
}
val := &Str{Name: []string{"first", "second"}, XXX: "ttt", Nested: NestedStr{BBB: "ddd", CCC: 9}}
field, err := StructFieldByPath(val, "Nested.CCC")
if err != nil {
t.Fatal(err)
}
if rfl.Indirect(reflect.ValueOf(field)).Int() != 9 {
t.Fatalf("invalid elem returned: %v", field)
}
}
func TestStructByTag(t *testing.T) { func TestStructByTag(t *testing.T) {
type Str struct { type Str struct {
Name []string `json:"name" codec:"flatten"` Name []string `json:"name" codec:"flatten"`