From bcaea675a779bcc448c9094f06c95262f665a539 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Mon, 25 Oct 2021 14:41:19 +0300 Subject: [PATCH] util/reflect: add method to zero struct field Signed-off-by: Vasiliy Tolstov --- util/reflect/struct.go | 63 ++++++++++++++++++++++++++++++++++++- util/reflect/struct_test.go | 40 +++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/util/reflect/struct.go b/util/reflect/struct.go index f020283c..0f580be8 100644 --- a/util/reflect/struct.go +++ b/util/reflect/struct.go @@ -69,6 +69,67 @@ func StructFieldByTag(src interface{}, tkey string, tval string) (interface{}, e 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 func StructFieldByPath(src interface{}, path string) (interface{}, error) { var err error @@ -98,7 +159,7 @@ func StructFieldByName(src interface{}, tkey string) (interface{}, error) { if len(fld.PkgPath) != 0 { 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() { val = val.Addr() } diff --git a/util/reflect/struct_test.go b/util/reflect/struct_test.go index 801e4475..85f5019b 100644 --- a/util/reflect/struct_test.go +++ b/util/reflect/struct_test.go @@ -8,6 +8,46 @@ import ( 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) { type NestedStr struct { BBB string