1
0
mirror of https://github.com/charlienet/go-mixed.git synced 2025-07-17 16:12:42 +08:00
This commit is contained in:
2022-04-24 17:06:40 +08:00
parent d3f727e54d
commit 78c957c98e
10 changed files with 270 additions and 83 deletions

View File

@ -1,7 +0,0 @@
package structs
func Copy(source, target any, opts ...optionFunc) {
opt := createOptions(opts)
_ = opt
}

View File

@ -6,7 +6,7 @@ func TestCopy(t *testing.T) {
v1 := struct{ Abc string }{Abc: "abc"}
v2 := struct{ Abc string }{}
Copy(v1, v2, IgnoreEmpty())
Copy(v1, &v2, IgnoreEmpty())
t.Log(v2)
}

29
structs/field.go Normal file
View File

@ -0,0 +1,29 @@
package structs
import (
"reflect"
"github.com/charlienet/go-mixed/expr"
)
type field struct {
name string
tagName string
ignoreEmpty bool
ignore bool
}
func parseField(fi reflect.StructField, opt option) field {
name, opts := parseTag(fi.Tag.Get(opt.TagName))
return field{
name: fi.Name,
tagName: expr.If(isValidTag(name), name, expr.If(opt.NameConverter != nil, opt.NameConverter(fi.Name), fi.Name)),
ignoreEmpty: opt.IgnoreEmpty || (opts.Contains("omitempty") && opt.Omitempty),
ignore: name == "-" && opt.Ignore,
}
}
func (f field) shouldIgnore(s reflect.Value) bool {
return f.ignore || (s.IsZero() && f.ignoreEmpty)
}

View File

@ -10,16 +10,18 @@ func TestStructToMap(t *testing.T) {
o := struct {
UserName string
InTagName string `json:"in_tag_name,omitempty"`
Ignore string `json:"-"`
KeepEmpty int
OmitEmpty int `json:",omitempty"`
}{
UserName: "测试字段",
InTagName: "具体名称",
Ignore: "这个字段跳过",
KeepEmpty: 0,
OmitEmpty: 0,
}
t.Log(structs.ToMap(o))
t.Log(structs.ToMap(o, structs.TagName("struct")))
t.Log(structs.ToMap(o, structs.IgnoreEmpty()))
t.Log(structs.ToMap(o, structs.Omitempty()))
t.Log(structs.ToMap(o, structs.Lcfirst()))

View File

@ -1,18 +1,24 @@
package structs
import (
"reflect"
"github.com/charlienet/go-mixed/json"
)
type optionFunc func(*option)
type option struct {
NameFunc func(string) string
IgnoreEmpty bool
DeepCopy bool
Omitempty bool
TagName string
DeepCopy bool
Omitempty bool
IgnoreEmpty bool
Ignore bool
NameConverter func(string) string
}
func TagName(name string) optionFunc {
return func(o *option) {
o.TagName = name
}
}
func IgnoreEmpty() optionFunc {
@ -21,42 +27,43 @@ func IgnoreEmpty() optionFunc {
}
}
func DeepCopy() optionFunc {
return func(o *option) {
o.DeepCopy = true
}
}
func Omitempty() optionFunc {
return func(o *option) {
o.Omitempty = true
}
}
func DeepCopy() optionFunc {
return func(o *option) {
o.DeepCopy = true
}
}
func Lcfirst() optionFunc {
return func(o *option) {
o.NameFunc = json.Lcfirst
o.NameConverter = json.Lcfirst
}
}
func Camel2Case() optionFunc {
return func(o *option) {
o.NameFunc = json.Camel2Case
o.NameConverter = json.Camel2Case
}
}
func createOptions(opts []optionFunc) *option {
o := &option{
NameFunc: func(s string) string { return s },
func defaultOptions() option {
return option{
TagName: defaultTagName,
Ignore: true,
NameConverter: func(s string) string { return s },
}
}
func acquireOptions(opts []optionFunc) option {
o := defaultOptions()
for _, f := range opts {
f(o)
f(&o)
}
return o
}
func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool {
return ignoreEmpty && v.IsZero()
}

21
structs/struct_test.go Normal file
View File

@ -0,0 +1,21 @@
package structs_test
import (
"reflect"
"testing"
"github.com/charlienet/go-mixed/structs"
"github.com/go-playground/assert/v2"
)
func TestNew(t *testing.T) {
o := struct {
Field1Name string
}{Field1Name: "field 1 name"}
s := structs.New(o)
assert.Equal(t, reflect.Struct, s.Kind())
t.Log(s.Names())
t.Log(s.Values())
}

View File

@ -1,35 +0,0 @@
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[opt.NameFunc(field.name)] = source.Interface()
}
return m
}

139
structs/structs.go Normal file
View File

@ -0,0 +1,139 @@
package structs
import (
"errors"
"reflect"
)
const defaultTagName = "json"
var (
ErrInvalidCopyDestination = errors.New("copy destination is invalid")
)
type Struct struct {
opt option
raw any
value reflect.Value
fields []field
}
func New(o any, opts ...optionFunc) *Struct {
opt := acquireOptions(opts)
v := indirect(reflect.ValueOf(o))
return &Struct{
opt: opt,
raw: o,
value: v,
fields: parseFields(v, opt),
}
}
func (s *Struct) Kind() reflect.Kind {
return s.value.Kind()
}
func (s *Struct) Names() []string {
names := make([]string, len(s.fields))
for i, f := range s.fields {
names[i] = f.name
}
return names
}
func (s *Struct) Values() []any {
values := make([]any, 0, len(s.fields))
for _, fi := range s.fields {
v := s.value.FieldByName(fi.name)
values = append(values, v.Interface())
}
return values
}
func (s *Struct) ToMap() map[string]any {
m := make(map[string]any, len(s.fields))
for _, fi := range s.fields {
source := s.value.FieldByName(fi.name)
if fi.shouldIgnore(source) {
continue
}
m[fi.tagName] = source.Interface()
}
return m
}
func (s *Struct) Copy(dest any) error {
to := indirect(reflect.ValueOf(dest))
if !to.CanAddr() {
return ErrInvalidCopyDestination
}
t := indirectType(reflect.TypeOf(dest))
for i := 0; i < t.NumField(); i++ {
destField := t.Field(i)
if fi, ok := s.getByName(destField.Name); ok {
source := s.value.FieldByName(fi.name)
if fi.shouldIgnore(source) {
continue
}
tv := to.FieldByName(destField.Name)
tv.Set(source)
}
}
return nil
}
func (s *Struct) getByName(name string) (field, bool) {
for i := range s.fields {
f := s.fields[i]
if f.name == name {
return f, true
}
}
return field{}, false
}
func ToMap(o any, opts ...optionFunc) map[string]any {
return New(o, opts...).ToMap()
}
func Copy(source, dst any, opts ...optionFunc) {
New(source, opts...).Copy(dst)
}
func parseFields(t reflect.Value, opt option) []field {
typ := indirectType(t.Type())
num := typ.NumField()
fields := make([]field, 0, num)
for i := 0; i < num; i++ {
fi := typ.Field(i)
fields = append(fields, parseField(fi, opt))
}
return fields
}
func indirect(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
return v
}
func indirectType(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
return t.Elem()
}
return t
}

View File

@ -1,11 +1,8 @@
package structs
import (
"reflect"
"strings"
"unicode"
"github.com/charlienet/go-mixed/expr"
)
type tagOptions string
@ -19,6 +16,7 @@ func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var name string
@ -27,23 +25,10 @@ func (o tagOptions) Contains(optionName string) bool {
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
@ -56,5 +41,6 @@ func isValidTag(s string) bool {
return false
}
}
return true
}

45
structs/tags_test.go Normal file
View File

@ -0,0 +1,45 @@
package structs
import "testing"
func TestParseTag_Name(t *testing.T) {
tags := []struct {
tag string
has bool
}{
{"", false},
{"name", true},
{"name,opt", true},
{"name , opt, opt2", false}, // has a single whitespace
{", opt, opt2", false},
}
for _, tag := range tags {
name, _ := parseTag(tag.tag)
if (name != "name") && tag.has {
t.Errorf("Parse tag should return name: %#v", tag)
}
}
}
func TestParseTag_Opts(t *testing.T) {
tags := []struct {
opts string
has bool
}{
{"name", false},
{"name,opt", true},
{"name , opt, opt2", false}, // has a single whitespace
{",opt, opt2", true},
{", opt3, opt4", false},
}
for _, tag := range tags {
_, opts := parseTag(tag.opts)
if opts.Contains("opt") != tag.has {
t.Errorf("Tag opts should have opt: %#v", tag)
}
}
}