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"
"regexp"
"strings"
"time"
)
// ErrInvalidParam specifies invalid url query params
@ -15,11 +14,12 @@ var ErrInvalidParam = errors.New("invalid url query param provided")
var bracketSplitter = regexp.MustCompile(`\[|\]`)
var timeKind = reflect.TypeOf(time.Time{}).Kind()
//var timeKind = reflect.ValueOf(time.Time{}).Kind()
type StructField struct {
Field reflect.StructField
Value reflect.Value
Path string
}
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++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 {
if len(fld.PkgPath) != 0 {
continue
}
@ -66,6 +66,17 @@ func StructFieldByTag(src interface{}, tkey string, tval string) (interface{}, e
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) {
sv := reflect.ValueOf(src)
if sv.Kind() == reflect.Ptr {
@ -79,7 +90,7 @@ func StructFieldByName(src interface{}, tkey string) (interface{}, error) {
for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 {
if len(fld.PkgPath) != 0 {
continue
}
if fld.Name == tkey {
@ -105,6 +116,19 @@ func StructFieldByName(src interface{}, tkey string) (interface{}, error) {
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
func StructFields(src interface{}) ([]StructField, error) {
var fields []StructField
@ -116,25 +140,29 @@ func StructFields(src interface{}) ([]StructField, error) {
if sv.Kind() != reflect.Struct {
return nil, ErrInvalidStruct
}
typ := sv.Type()
for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 {
if !val.IsValid() || len(fld.PkgPath) != 0 {
continue
}
switch val.Kind() {
case timeKind:
fields = append(fields, StructField{Field: fld, Value: val})
//case timeKind:
//fmt.Printf("GGG\n")
//fields = append(fields, StructField{Field: fld, Value: val, Path: fld.Name})
case reflect.Struct:
infields, err := StructFields(val.Interface())
if err != nil {
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:
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++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if !val.CanSet() || len(fld.PkgPath) != 0 || !val.IsValid() {
if len(fld.PkgPath) != 0 || !val.IsValid() {
continue
}

View File

@ -2,9 +2,80 @@ package reflect
import (
"net/url"
"reflect"
rfl "reflect"
"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) {
type Str struct {
Name []string `json:"name" codec:"flatten"`