util/reflect: add method to zero struct field

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2021-10-25 14:41:19 +03:00
parent 3087ba1d73
commit bcaea675a7
2 changed files with 102 additions and 1 deletions

View File

@ -69,6 +69,67 @@ func StructFieldByTag(src interface{}, tkey string, tval string) (interface{}, e
return nil, ErrNotFound return nil, ErrNotFound
} }
// ZeroFieldByPath clean struct field by its path
func ZeroFieldByPath(src interface{}, path string) error {
var err error
var val reflect.Value
for _, p := range strings.Split(path, ".") {
val, err = structValueByName(reflect.ValueOf(src), p)
if err != nil {
return err
}
}
if IsEmpty(val) {
return nil
}
if !val.CanSet() {
return ErrInvalidStruct
}
val.Set(reflect.Zero(val.Type()))
return nil
}
// structValueByName get struct field by its name
func structValueByName(sv reflect.Value, tkey string) (reflect.Value, error) {
if sv.Kind() == reflect.Ptr {
sv = sv.Elem()
}
if sv.Kind() != reflect.Struct {
return reflect.Zero(reflect.TypeOf(sv)), ErrInvalidStruct
}
typ := sv.Type()
for idx := 0; idx < typ.NumField(); idx++ {
fld := typ.Field(idx)
val := sv.Field(idx)
if len(fld.PkgPath) != 0 {
continue
}
if fld.Name == tkey || strings.EqualFold(strings.ToLower(fld.Name), strings.ToLower(tkey)) {
return val, nil
}
switch val.Kind() {
case reflect.Ptr:
if val = val.Elem(); val.Kind() == reflect.Struct {
if iface, err := structValueByName(val, tkey); err == nil {
return iface, nil
}
}
case reflect.Struct:
if iface, err := structValueByName(val, tkey); err == nil {
return iface, nil
}
}
}
return reflect.Zero(reflect.TypeOf(sv)), ErrNotFound
}
// StructFieldByPath get struct field by its path // StructFieldByPath get struct field by its path
func StructFieldByPath(src interface{}, path string) (interface{}, error) { func StructFieldByPath(src interface{}, path string) (interface{}, error) {
var err error var err error
@ -98,7 +159,7 @@ func StructFieldByName(src interface{}, tkey string) (interface{}, error) {
if len(fld.PkgPath) != 0 { if len(fld.PkgPath) != 0 {
continue continue
} }
if fld.Name == tkey { if fld.Name == tkey || strings.EqualFold(strings.ToLower(fld.Name), strings.ToLower(tkey)) {
if val.Kind() != reflect.Ptr && val.CanAddr() { if val.Kind() != reflect.Ptr && val.CanAddr() {
val = val.Addr() val = val.Addr()
} }

View File

@ -8,6 +8,46 @@ import (
rutil "go.unistack.org/micro/v3/util/reflect" rutil "go.unistack.org/micro/v3/util/reflect"
) )
func TestZeroFieldByPath(t *testing.T) {
type NestedStr struct {
BBB string
CCC int
}
type Str1 struct {
Name []string `json:"name" codec:"flatten"`
XXX string `json:"xxx"`
Nested NestedStr
}
type Str2 struct {
XXX string `json:"xxx"`
Nested *NestedStr
Name []string `json:"name" codec:"flatten"`
}
var err error
val1 := &Str1{Name: []string{"first", "second"}, XXX: "ttt", Nested: NestedStr{BBB: "ddd", CCC: 9}}
err = rutil.ZeroFieldByPath(val1, "name.nested.bbb")
if err != nil {
t.Fatal(err)
}
err = rutil.ZeroFieldByPath(val1, "name.nested")
if err != nil {
t.Fatal(err)
}
if val1.Nested.BBB == "ddd" {
t.Fatalf("zero field not works: %v", val1)
}
val2 := &Str2{Name: []string{"first", "second"}, XXX: "ttt", Nested: &NestedStr{BBB: "ddd", CCC: 9}}
err = rutil.ZeroFieldByPath(val2, "name.nested")
if err != nil {
t.Fatal(err)
}
if val2.Nested != nil {
t.Fatalf("zero field not works: %v", val2)
}
}
func TestStructFieldsMap(t *testing.T) { func TestStructFieldsMap(t *testing.T) {
type NestedStr struct { type NestedStr struct {
BBB string BBB string