diff --git a/structs/copy.go b/structs/copy.go new file mode 100644 index 0000000..ad83bdd --- /dev/null +++ b/structs/copy.go @@ -0,0 +1,7 @@ +package structs + +func Copy(source, target any, opts ...optionFunc) { + opt := createOptions(opts) + + _ = opt +} diff --git a/structs/copy_test.go b/structs/copy_test.go new file mode 100644 index 0000000..dcb077f --- /dev/null +++ b/structs/copy_test.go @@ -0,0 +1,12 @@ +package structs + +import "testing" + +func TestCopy(t *testing.T) { + v1 := struct{ Abc string }{Abc: "abc"} + v2 := struct{ Abc string }{} + + Copy(v1, v2, IgnoreEmpty()) + + t.Log(v2) +} diff --git a/structs/struct_to_map.go b/structs/struct_to_map.go new file mode 100644 index 0000000..84c2baa --- /dev/null +++ b/structs/struct_to_map.go @@ -0,0 +1,35 @@ +package structs + +import ( + "reflect" +) + +const tagName = "json" + +func ToMap(o any, opts ...optionFunc) map[string]any { + typ := reflect.TypeOf(o) + + kind := typ.Kind() + if kind == reflect.Map { + if h, ok := o.(map[string]any); ok { + return h + } + } + + opt := createOptions(opts) + val := reflect.ValueOf(o) + m := make(map[string]any) + for i := 0; i < val.NumField(); i++ { + fi := typ.Field(i) + + field := getFieldOption(fi) + source := val.FieldByName(fi.Name) + if shouldIgnore(source, opt.IgnoreEmpty || field.omitEmpty && opt.Omitempty) { + continue + } + + m[field.name] = source.Interface() + } + + return m +} diff --git a/structs/struct_to_map_test.go b/structs/struct_to_map_test.go new file mode 100644 index 0000000..9c67249 --- /dev/null +++ b/structs/struct_to_map_test.go @@ -0,0 +1,25 @@ +package structs_test + +import ( + "testing" + + "github.com/charlienet/go-mixed/structs" +) + +func TestStructToMap(t *testing.T) { + o := struct { + Abc string + InTagName string `json:"in_tag_name,omitempty"` + KeepEmpty int + OmitEmpty int `json:",omitempty"` + }{ + Abc: "测试字段", + InTagName: "具体名称", + KeepEmpty: 0, + OmitEmpty: 0, + } + + t.Log(structs.ToMap(o)) + t.Log(structs.ToMap(o, structs.IgnoreEmpty())) + t.Log(structs.ToMap(o, structs.Omitempty())) +} diff --git a/structs/tags.go b/structs/tags.go new file mode 100644 index 0000000..be70b7d --- /dev/null +++ b/structs/tags.go @@ -0,0 +1,60 @@ +package structs + +import ( + "reflect" + "strings" + "unicode" + + "github.com/charlienet/go-mixed/expr" +) + +type tagOptions string + +func parseTag(tag string) (string, tagOptions) { + tag, opt, _ := strings.Cut(tag, ",") + return tag, tagOptions(opt) +} + +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var name string + name, s, _ = strings.Cut(s, ",") + if name == optionName { + return true + } + } + return false +} + +type fieldOption struct { + name string + omitEmpty bool +} + +func getFieldOption(fi reflect.StructField) fieldOption { + name, opts := parseTag(fi.Tag.Get(tagName)) + + return fieldOption{ + name: expr.If(isValidTag(name), name, fi.Name), + omitEmpty: opts.Contains("omitempty"), + } +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c): + case !unicode.IsLetter(c) && !unicode.IsDigit(c): + return false + } + } + return true +} diff --git a/structs/utils.go b/structs/utils.go new file mode 100644 index 0000000..86b5e6e --- /dev/null +++ b/structs/utils.go @@ -0,0 +1,45 @@ +package structs + +import ( + "reflect" +) + +type optionFunc func(*option) + +type option struct { + IgnoreEmpty bool + DeepCopy bool + Omitempty bool +} + +func IgnoreEmpty() optionFunc { + return func(o *option) { + o.IgnoreEmpty = true + } +} + +func DeepCopy() optionFunc { + return func(o *option) { + o.DeepCopy = true + } +} + +func Omitempty() optionFunc { + return func(o *option) { + o.Omitempty = true + } +} + +func createOptions(opts []optionFunc) *option { + o := &option{} + for _, f := range opts { + f(o) + } + + return o +} + +func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool { + return ignoreEmpty && v.IsZero() +} +