diff --git a/structs/structs.go b/structs/structs.go index 2dafd45e9..a40d53163 100644 --- a/structs/structs.go +++ b/structs/structs.go @@ -1,28 +1,86 @@ package structs -import "github.com/gookit/goutil/internal/comfunc" +import ( + "errors" + "reflect" -// ToMap simple convert structs to map by reflect -func ToMap(st interface{}) map[string]interface{} { - mp, _ := comfunc.TryStructToMap(st) - return mp -} + "github.com/gookit/goutil/reflects" +) + +// MapStruct simple copy src struct value to dst struct +// func MapStruct(srcSt, dstSt interface{}) { +// // TODO +// } -// TryToMap simple convert structs to map by reflect -func TryToMap(st interface{}) (map[string]interface{}, error) { - return comfunc.TryStructToMap(st) +const defaultInitTag = "default" + +// InitOptions struct +type InitOptions struct { + TagName string } -// MustToMap alis of TryToMap, but will panic on error -func MustToMap(st interface{}) map[string]interface{} { - mp, err := comfunc.TryStructToMap(st) - if err != nil { - panic(err) +// InitDefaults init struct default value by field "default" tag. +// +// Example: +// +// type User1 struct { +// Name string `default:"inhere"` +// Age int32 `default:"30"` +// } +// +// u1 := &User1{} +// err = structs.InitDefaults(u1, nil) +// fmt.Printf("%+v\n", u1) +// // Output: {Name:inhere Age:30} +func InitDefaults(ptr interface{}, opt *InitOptions) error { + rv := reflect.ValueOf(ptr) + if rv.Kind() != reflect.Ptr { + return errors.New("must be provider an pointer") + } + + rv = rv.Elem() + if rv.Kind() != reflect.Struct { + return errors.New("must be provider an struct") } - return mp + + if opt == nil { + opt = &InitOptions{TagName: defaultInitTag} + } else if opt.TagName == "" { + opt.TagName = defaultInitTag + } + + return initDefaults(rv, opt.TagName) } -// MapStruct simple copy src struct value to dst struct -// func MapStruct(srcSt, dstSt interface{}) { -// // TODO -// } +func initDefaults(rv reflect.Value, tagName string) error { + rt := rv.Type() + + for i := 0; i < rt.NumField(); i++ { + ft := rt.Field(i) + // skip don't exported field + if ft.Name[0] >= 'a' && ft.Name[0] <= 'z' { + continue + } + + fv := rv.Field(i) + if fv.Kind() == reflect.Struct { + err := initDefaults(fv, tagName) + if err != nil { + return err + } + continue + } + + tagVal, ok := ft.Tag.Lookup(tagName) + if ok && tagVal != "" && fv.CanSet() { + val, err := reflects.ValueByKind(tagVal, fv.Kind()) + if err != nil { + return err + } + + fv.Set(val) + } + } + + return nil +} diff --git a/structs/structs_test.go b/structs/structs_test.go index 23239466a..c914f4e31 100644 --- a/structs/structs_test.go +++ b/structs/structs_test.go @@ -3,44 +3,36 @@ package structs_test import ( "testing" - "github.com/gookit/goutil/dump" "github.com/gookit/goutil/structs" "github.com/gookit/goutil/testutil/assert" ) -func TestTryToMap(t *testing.T) { - mp, err := structs.TryToMap(nil) - assert.Empty(t, mp) - assert.NoErr(t, err) - +func TestInitDefaults(t *testing.T) { type User struct { - Name string - Age int - city string - } - - u := User{ - Name: "inhere", - Age: 34, - city: "somewhere", + Name string `default:"inhere"` + Age int `default:""` + city string `default:""` } - mp, err = structs.TryToMap(u) + u := &User{} + err := structs.InitDefaults(u, nil) assert.NoErr(t, err) - dump.P(mp) - assert.Contains(t, mp, "Name") - assert.Contains(t, mp, "Age") - assert.NotContains(t, mp, "city") - - mp, err = structs.TryToMap(&u) - assert.NoErr(t, err) - dump.P(mp) + assert.Eq(t, "inhere", u.Name) + assert.Eq(t, 0, u.Age) + // dump.P(u) + + type User1 struct { + Name string `default:"inhere"` + Age int32 `default:"30"` + city string `default:"val0"` + } - mp = structs.ToMap(&u) + u1 := &User1{} + err = structs.InitDefaults(u1, nil) assert.NoErr(t, err) - dump.P(mp) - - assert.Panics(t, func() { - structs.MustToMap("abc") - }) + assert.Eq(t, "inhere", u1.Name) + assert.Eq(t, int32(30), u1.Age) + assert.Eq(t, "", u1.city) + // dump.P(u1) + // fmt.Printf("%+v\n", u1) }