1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-04 12:52:28 +08:00

Merge branch 'main' into v2

This commit is contained in:
dudaodong
2023-03-15 14:43:48 +08:00
9 changed files with 473 additions and 20 deletions

0
docs/structutil/field.md Normal file
View File

View File

14
pointer/pointer.go Normal file
View File

@@ -0,0 +1,14 @@
package pointer
import "reflect"
// ExtractPointer returns the underlying value by the given interface type
func ExtractPointer(value any) any {
t := reflect.TypeOf(value)
v := reflect.ValueOf(value)
if t.Kind() != reflect.Pointer {
return value
}
return ExtractPointer(v.Elem().Interface())
}

18
pointer/pointer_test.go Normal file
View File

@@ -0,0 +1,18 @@
package pointer
import (
"github.com/duke-git/lancet/v2/internal"
"testing"
)
func TestExtractPointer(t *testing.T) {
assert := internal.NewAssert(t, "TestExtractPointer")
a := 1
b := &a
c := &b
d := &c
assert.Equal(1, ExtractPointer(d))
}

7
structutil/error.go Normal file
View File

@@ -0,0 +1,7 @@
package structutil
import "fmt"
func ErrInvalidStruct(v any) error {
return fmt.Errorf("invalid struct %v", v)
}

View File

@@ -1,20 +1,25 @@
package structutil
import "reflect"
import (
"github.com/duke-git/lancet/v2/pointer"
"reflect"
)
type Field struct {
value reflect.Value
Struct
field reflect.StructField
tag *Tag
}
func newField(v reflect.Value, f reflect.StructField, tagName string) *Field {
tag := f.Tag.Get(tagName)
return &Field{
value: v,
field := &Field{
field: f,
tag: newTag(tag),
}
field.rvalue = v
field.TagName = tagName
return field
}
// Tag returns the value that the key in the tag string.
@@ -24,22 +29,22 @@ func (f *Field) Tag() *Tag {
// Value returns the underlying value of the field.
func (f *Field) Value() any {
return f.value.Interface()
return f.rvalue.Interface()
}
// IsEmbedded returns true if the given field is an embedded field.
func (f *Field) IsEmbedded() bool {
return f.field.Anonymous
return len(f.field.Index) > 1
}
// IsExported returns true if the given field is exported.
func (f *Field) IsExported() bool {
return f.field.PkgPath == ""
return f.field.IsExported()
}
// IsZero returns true if the given field is zero value.
func (f *Field) IsZero() bool {
z := reflect.Zero(f.value.Type()).Interface()
z := reflect.Zero(f.rvalue.Type()).Interface()
v := f.Value()
return reflect.DeepEqual(z, v)
}
@@ -51,5 +56,52 @@ func (f *Field) Name() string {
// Kind returns the field's kind
func (f *Field) Kind() reflect.Kind {
return f.value.Kind()
return f.rvalue.Kind()
}
func (f *Field) IsSlice() bool {
k := f.rvalue.Kind()
return k == reflect.Slice
}
func (f *Field) MapValue(value any) any {
val := pointer.ExtractPointer(value)
v := reflect.ValueOf(val)
var ret any
switch v.Kind() {
case reflect.Struct:
s := New(val)
s.TagName = f.TagName
m, _ := s.ToMap()
ret = m
case reflect.Map:
mapEl := v.Type().Elem()
switch mapEl.Kind() {
case reflect.Ptr, reflect.Array, reflect.Map, reflect.Slice, reflect.Chan:
// iterate the map
m := make(map[string]any, v.Len())
for _, key := range v.MapKeys() {
m[key.String()] = f.MapValue(v.MapIndex(key).Interface())
}
ret = m
default:
ret = v.Interface()
}
case reflect.Slice, reflect.Array:
sEl := v.Type().Elem()
switch sEl.Kind() {
case reflect.Ptr, reflect.Array, reflect.Map, reflect.Slice, reflect.Chan:
slices := make([]any, v.Len())
for i := 0; i < v.Len(); i++ {
slices[i] = f.MapValue(v.Index(i).Interface())
}
ret = slices
default:
ret = v.Interface()
}
default:
ret = v.Interface()
}
return ret
}

272
structutil/field_test.go Normal file
View File

@@ -0,0 +1,272 @@
package structutil
import (
"github.com/duke-git/lancet/v2/internal"
"reflect"
"testing"
)
func TestField_Tag(t *testing.T) {
assert := internal.NewAssert(t, "TestField_Tag")
type Parent struct {
Name string `json:"name,omitempty"`
}
p1 := &Parent{"111"}
s := New(p1)
n, _ := s.Field("Name")
tag := n.Tag()
assert.Equal("name", tag.Name)
assert.Equal(true, tag.HasOption("omitempty"))
}
func TestField_Value(t *testing.T) {
assert := internal.NewAssert(t, "TestField_Value")
type Parent struct {
Name string `json:"name,omitempty"`
}
p1 := &Parent{"111"}
s := New(p1)
n, _ := s.Field("Name")
assert.Equal("111", n.Value())
}
func TestField_IsEmbedded(t *testing.T) {
assert := internal.NewAssert(t, "TestField_IsEmbedded")
type Parent struct {
Name string
}
type Child struct {
Parent
Age int
}
c1 := &Child{}
c1.Name = "111"
c1.Age = 11
s := New(c1)
n, _ := s.Field("Name")
a, _ := s.Field("Age")
assert.Equal(true, n.IsEmbedded())
assert.Equal(false, a.IsEmbedded())
}
func TestField_IsExported(t *testing.T) {
assert := internal.NewAssert(t, "TestField_IsEmbedded")
type Parent struct {
Name string
age int
}
p1 := &Parent{Name: "11", age: 11}
s := New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("age")
assert.Equal(true, n.IsExported())
assert.Equal(false, a.IsExported())
}
func TestField_IsZero(t *testing.T) {
assert := internal.NewAssert(t, "TestField_IsZero")
type Parent struct {
Name string
Age int
}
p1 := &Parent{Age: 11}
s := New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("Age")
assert.Equal(true, n.IsZero())
assert.Equal(false, a.IsZero())
}
func TestField_Name(t *testing.T) {
assert := internal.NewAssert(t, "TestField_Name")
type Parent struct {
Name string
Age int
}
p1 := &Parent{Age: 11}
s := New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("Age")
assert.Equal("Name", n.Name())
assert.Equal("Age", a.Name())
}
func TestField_Kind(t *testing.T) {
assert := internal.NewAssert(t, "TestField_Kind")
type Parent struct {
Name string
Age int
}
p1 := &Parent{Age: 11}
s := New(p1)
n, _ := s.Field("Name")
a, _ := s.Field("Age")
assert.Equal(reflect.String, n.Kind())
assert.Equal(reflect.Int, a.Kind())
}
func TestField_IsSlice(t *testing.T) {
assert := internal.NewAssert(t, "TestField_IsSlice")
type Parent struct {
Name string
arr []int
}
p1 := &Parent{arr: []int{1, 2, 3}}
s := New(p1)
a, _ := s.Field("arr")
assert.Equal(true, a.IsSlice())
}
func TestField_MapValue(t *testing.T) {
assert := internal.NewAssert(t, "TestField_MapValue")
t.Run("nested struct", func(t *testing.T) {
type Child struct {
Name string `json:"name"`
}
type Parent struct {
Name string `json:"name"`
Child *Child `json:"child"`
}
c1 := &Child{"11-1"}
p1 := &Parent{
Name: "11",
Child: c1,
}
s := New(p1)
f, ok := s.Field("Child")
val := f.MapValue(f.Value())
assert.Equal(true, ok)
assert.Equal(map[string]any{"name": "11-1"}, val)
})
t.Run("nested ptr struct", func(t *testing.T) {
type Child struct {
Name string `json:"name"`
}
type Parent struct {
Name string `json:"name"`
Child any `json:"child"`
}
c1 := &Child{"11-1"}
c2 := &c1
c3 := &c2
p1 := &Parent{
Name: "11",
Child: c3,
}
s := New(p1)
f, ok := s.Field("Child")
val := f.MapValue(f.Value())
assert.Equal(true, ok)
assert.Equal(map[string]any{"name": "11-1"}, val)
})
t.Run("nested array", func(t *testing.T) {
type Parent struct {
Name string `json:"name"`
Child []int `json:"child"`
}
p1 := &Parent{
Name: "11",
Child: []int{1, 2, 3},
}
s := New(p1)
f, ok := s.Field("Child")
val := f.MapValue(f.Value())
assert.Equal(true, ok)
assert.Equal([]int{1, 2, 3}, val)
})
t.Run("nested array in struct", func(t *testing.T) {
type Child struct {
Name string `json:"name"`
}
type Parent struct {
Name string `json:"name"`
Child []*Child `json:"child"`
}
c1 := &Child{"11-1"}
c2 := &Child{"11-2"}
p1 := &Parent{
Name: "11",
Child: []*Child{c1, c2},
}
s := New(p1)
f, ok := s.Field("Child")
val := f.MapValue(f.Value())
assert.Equal(true, ok)
arr := []any{map[string]any{"name": "11-1"}, map[string]any{"name": "11-2"}}
assert.Equal(arr, val)
})
t.Run("nested ptr array in struct", func(t *testing.T) {
type Child struct {
Name string `json:"name"`
}
type Parent struct {
Name string `json:"name"`
Child *[]*Child `json:"child"`
}
c1 := &Child{"11-1"}
c2 := &Child{"11-2"}
p1 := &Parent{
Name: "11",
Child: &[]*Child{c1, c2},
}
s := New(p1)
f, ok := s.Field("Child")
val := f.MapValue(f.Value())
assert.Equal(true, ok)
arr := []any{map[string]any{"name": "11-1"}, map[string]any{"name": "11-2"}}
assert.Equal(arr, val)
})
t.Run("nested map in struct", func(t *testing.T) {
type Parent struct {
Name string `json:"name"`
Child map[string]any `json:"child"`
}
p1 := &Parent{
Name: "11",
Child: map[string]any{"a": 1, "b": map[string]any{"name": "11-1"}},
}
s := New(p1)
f, ok := s.Field("Child")
val := f.MapValue(f.Value())
assert.Equal(true, ok)
assert.Equal(map[string]any{"a": 1, "b": map[string]any{"name": "11-1"}}, val)
})
}

View File

@@ -1,6 +1,7 @@
package structutil
import (
"github.com/duke-git/lancet/v2/pointer"
"reflect"
)
@@ -17,11 +18,9 @@ type Struct struct {
// New returns a new *Struct
func New(value any) *Struct {
value = pointer.ExtractPointer(value)
v := reflect.ValueOf(value)
t := reflect.TypeOf(value)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
return &Struct{
raw: value,
rtype: t,
@@ -49,8 +48,11 @@ func New(value any) *Struct {
//
// Only the exported fields of a struct can be converted.
func (s *Struct) ToMap() (map[string]any, error) {
result := make(map[string]any)
if !s.IsStruct() {
return nil, ErrInvalidStruct(s)
}
result := make(map[string]any)
fields := s.Fields()
for _, f := range fields {
if !f.IsExported() || f.tag.IsEmpty() || f.tag.Name == "-" {
@@ -59,8 +61,7 @@ func (s *Struct) ToMap() (map[string]any, error) {
if f.IsZero() && f.tag.HasOption("omitempty") {
continue
}
// TODO: sub struct
result[f.tag.Name] = f.Value()
result[f.tag.Name] = f.MapValue(f.Value())
}
return result, nil
@@ -68,19 +69,35 @@ func (s *Struct) ToMap() (map[string]any, error) {
// Fields returns all the struct fields within a slice
func (s *Struct) Fields() []*Field {
var fields []*Field
fieldNum := s.rvalue.NumField()
for i := 0; i < fieldNum; i++ {
v := s.rvalue.Field(i)
sf := s.rtype.Field(i)
field := newField(v, sf, DefaultTagName)
field := newField(v, sf, s.TagName)
fields = append(fields, field)
}
return fields
}
// Field returns a Field if the given field name was found
func (s *Struct) Field(name string) (*Field, bool) {
f, ok := s.rtype.FieldByName(name)
if !ok {
return nil, false
}
return newField(s.rvalue.FieldByName(name), f, s.TagName), true
}
// IsStruct returns true if the given rvalue is a struct
func (s *Struct) IsStruct() bool {
k := s.rvalue.Kind()
if k == reflect.Invalid {
return false
}
return k == reflect.Struct
}
// ToMap convert struct to map, only convert exported struct field
// map key is specified same as struct field tag `json` value.
func ToMap(v any) (map[string]any, error) {

View File

@@ -2,11 +2,18 @@ package structutil
import (
"github.com/duke-git/lancet/v2/internal"
"reflect"
"testing"
)
func TestToMap(t *testing.T) {
assert := internal.NewAssert(t, "TestStructToMap")
func TestStruct_ToMap(t *testing.T) {
assert := internal.NewAssert(t, "TestStruct_ToMap")
t.Run("no struct", func(t *testing.T) {
m, _ := ToMap(1)
var expected map[string]any
assert.Equal(expected, m)
})
t.Run("StructToMap", func(_ *testing.T) {
type People struct {
@@ -55,3 +62,69 @@ func TestToMap(t *testing.T) {
assert.Equal(expect2, p2m)
})
}
func TestStruct_Fields(t *testing.T) {
assert := internal.NewAssert(t, "TestStruct_Fields")
type Parent struct {
A string `json:"a"`
B int `json:"b"`
C []string `json:"c"`
D map[string]any `json:"d"`
}
p1 := &Parent{
A: "1",
B: 11,
C: []string{"11", "22"},
D: map[string]any{"d1": 1, "d2": 2},
}
s := New(p1)
fields := s.Fields()
assert.Equal(4, len(fields))
assert.Equal(reflect.String, fields[0].Kind())
assert.Equal(reflect.Int, fields[1].Kind())
assert.Equal(reflect.Slice, fields[2].Kind())
assert.Equal(reflect.Map, fields[3].Kind())
}
func TestStruct_Field(t *testing.T) {
assert := internal.NewAssert(t, "TestStruct_Field")
type Parent struct {
A string `json:"a"`
B int `json:"b"`
C []string `json:"c"`
D map[string]any `json:"d"`
}
p1 := &Parent{
A: "1",
B: 11,
C: []string{"11", "22"},
D: map[string]any{"d1": 1, "d2": 2},
}
s := New(p1)
a, ok := s.Field("A")
assert.Equal(true, ok)
assert.Equal(reflect.String, a.Kind())
assert.Equal("1", a.Value())
assert.Equal("a", a.tag.Name)
assert.Equal(false, a.tag.HasOption("omitempty"))
}
func TestStruct_IsStruct(t *testing.T) {
assert := internal.NewAssert(t, "TestStruct_Field")
type Test1 struct{}
t1 := &Test1{}
t2 := 1
s1 := New(t1)
s2 := New(t2)
assert.Equal(true, s1.IsStruct())
assert.Equal(false, s2.IsStruct())
}