Skip to content

Commit

Permalink
👔 up: reflects - update some util func and test
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Sep 1, 2023
1 parent 7888c03 commit 362bdcb
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 139 deletions.
3 changes: 3 additions & 0 deletions reflects/conv.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) {
}
case reflect.Int32:
if dstV, err1 := mathutil.ToInt(val); err1 == nil {
if dstV > math.MaxInt32 {
return rv, fmt.Errorf("value overflow int32. val: %v", val)
}
rv = reflect.ValueOf(int32(dstV))
}
case reflect.Int64:
Expand Down
83 changes: 83 additions & 0 deletions reflects/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package reflects

import (
"reflect"
"strconv"
)

// EachMap process any map data
func EachMap(mp reflect.Value, fn func(key, val reflect.Value)) {
if fn == nil {
return
}
if mp.Kind() != reflect.Map {
panic("only allow map value data")
}

for _, key := range mp.MapKeys() {
fn(key, mp.MapIndex(key))
}
}

// EachStrAnyMap process any map data as string key and any value
func EachStrAnyMap(mp reflect.Value, fn func(key string, val any)) {
EachMap(mp, func(key, val reflect.Value) {
fn(String(key), val.Interface())
})
}

// FlatFunc custom collect handle func
type FlatFunc func(path string, val reflect.Value)

// FlatMap process tree map to flat key-value map.
//
// Examples:
//
// {"top": {"sub": "value", "sub2": "value2"} }
// ->
// {"top.sub": "value", "top.sub2": "value2" }
func FlatMap(rv reflect.Value, fn FlatFunc) {
if fn == nil {
return
}

if rv.Kind() != reflect.Map {
panic("only allow flat map data")
}
flatMap(rv, fn, "")
}

func flatMap(rv reflect.Value, fn FlatFunc, parent string) {
for _, key := range rv.MapKeys() {
path := String(key)
if parent != "" {
path = parent + "." + path
}

fv := Indirect(rv.MapIndex(key))
switch fv.Kind() {
case reflect.Map:
flatMap(fv, fn, path)
case reflect.Array, reflect.Slice:
flatSlice(fv, fn, path)
default:
fn(path, fv)
}
}
}

func flatSlice(rv reflect.Value, fn FlatFunc, parent string) {
for i := 0; i < rv.Len(); i++ {
path := parent + "[" + strconv.Itoa(i) + "]"
fv := Indirect(rv.Index(i))

switch fv.Kind() {
case reflect.Map:
flatMap(fv, fn, path)
case reflect.Array, reflect.Slice:
flatSlice(fv, fn, path)
default:
fn(path, fv)
}
}
}
64 changes: 64 additions & 0 deletions reflects/map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package reflects_test

import (
"reflect"
"testing"

"github.com/gookit/goutil/dump"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/testutil/assert"
)

func TestFlatMap(t *testing.T) {
assert.Panics(t, func() {
reflects.FlatMap(reflect.ValueOf("abc"), func(_ string, _ reflect.Value) {
// nothing
})
})

assert.NotPanics(t, func() {
reflects.FlatMap(reflect.ValueOf(nil), nil)
})

mp := map[string]any{
"name": "inhere",
"age": 234,
"top": map[string]any{
"sub0": "val0",
"sub1": []string{"val1-0", "val1-1"},
},
}

flatMp := make(map[string]any, len(mp)*2)
reflects.FlatMap(reflect.ValueOf(mp), func(path string, val reflect.Value) {
flatMp[path] = val.Interface()
})
dump.P(flatMp)
assert.Eq(t, "inhere", flatMp["name"])
assert.Eq(t, "val0", flatMp["top.sub0"])
assert.Eq(t, "val1-0", flatMp["top.sub1[0]"])
}

func TestEachStrAnyMap(t *testing.T) {
smp := map[int]string{
1: "val1",
2: "val2",
}

mp := make(map[string]any)
reflects.EachStrAnyMap(reflect.ValueOf(smp), func(key string, val any) {
mp[key] = val
})

assert.Eq(t, "val1", mp["1"])
assert.Eq(t, "val2", mp["2"])

assert.NotPanics(t, func() {
reflects.EachMap(reflect.ValueOf("abc"), nil)
})
assert.Panics(t, func() {
reflects.EachMap(reflect.ValueOf("abc"), func(key, val reflect.Value) {
// do nothing
})
})
}
61 changes: 61 additions & 0 deletions reflects/slice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package reflects

import (
"fmt"
"reflect"
)

// MakeSliceByElem create a new slice by the element type.
//
// - elType: the type of the element.
// - returns: the new slice.
//
// Usage:
//
// sl := MakeSliceByElem(reflect.TypeOf(1), 10, 20)
// sl.Index(0).SetInt(10)
//
// // Or use reflect.AppendSlice() merge two slice
// // Or use `for` with `reflect.Append()` add elements
func MakeSliceByElem(elTyp reflect.Type, len, cap int) reflect.Value {
return reflect.MakeSlice(reflect.SliceOf(elTyp), len, cap)
}

// FlatSlice flatten multi-level slice to given depth-level slice.
//
// Example:
//
// FlatSlice([]any{ []any{3, 4}, []any{5, 6} }, 1) // Output: []any{3, 4, 5, 6}
//
// always return reflect.Value of []any. note: maybe flatSl.Cap != flatSl.Len
func FlatSlice(sl reflect.Value, depth int) reflect.Value {
items := make([]reflect.Value, 0, sl.Cap())
slCap := addSliceItem(sl, depth, func(item reflect.Value) {
items = append(items, item)
})

flatSl := reflect.MakeSlice(reflect.SliceOf(anyType), 0, slCap)
flatSl = reflect.Append(flatSl, items...)

return flatSl
}

func addSliceItem(sl reflect.Value, depth int, collector func(item reflect.Value)) (c int) {
for i := 0; i < sl.Len(); i++ {
v := Elem(sl.Index(i))

if depth > 0 {
if v.Kind() != reflect.Slice {
panic(fmt.Sprintf("depth: %d, the value of index %d is not slice", depth, i))
}
c += addSliceItem(v, depth-1, collector)
} else {
collector(v)
}
}

if depth == 0 {
c = sl.Cap()
}
return c
}
25 changes: 25 additions & 0 deletions reflects/slice_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package reflects_test

import (
"reflect"
"testing"

"github.com/gookit/goutil/dump"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/testutil/assert"
)

func TestMakeSliceByElem(t *testing.T) {
slv := reflects.MakeSliceByElem(reflect.TypeOf("str"), 0, 2)
slv = reflect.Append(slv, reflect.ValueOf("a"))
slv = reflect.Append(slv, reflect.ValueOf("b"))

sl := slv.Interface().([]string)
dump.P(sl)
assert.Len(t, sl, 2)
assert.Eq(t, "a", sl[0])
}

func TestFlatSlice(t *testing.T) {

}
89 changes: 12 additions & 77 deletions reflects/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ func Indirect(v reflect.Value) reflect.Value {
return v
}

// UnwrapAny unwrap reflect.Interface value. otherwise, will return self
func UnwrapAny(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
return v.Elem()
}

if v.IsNil() {
return emptyValue
}
return v
}

// TypeReal returns a ptr type's real type. otherwise, will return self.
func TypeReal(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Pointer {
Expand Down Expand Up @@ -144,80 +156,3 @@ func SetRValue(rv, val reflect.Value) {

rv.Set(val)
}

// EachMap process any map data
func EachMap(mp reflect.Value, fn func(key, val reflect.Value)) {
if fn == nil {
return
}
if mp.Kind() != reflect.Map {
panic("only allow map value data")
}

for _, key := range mp.MapKeys() {
fn(key, mp.MapIndex(key))
}
}

// EachStrAnyMap process any map data as string key and any value
func EachStrAnyMap(mp reflect.Value, fn func(key string, val any)) {
EachMap(mp, func(key, val reflect.Value) {
fn(String(key), val.Interface())
})
}

// FlatFunc custom collect handle func
type FlatFunc func(path string, val reflect.Value)

// FlatMap process tree map to flat key-value map.
//
// Examples:
//
// {"top": {"sub": "value", "sub2": "value2"} }
// ->
// {"top.sub": "value", "top.sub2": "value2" }
func FlatMap(rv reflect.Value, fn FlatFunc) {
if fn == nil {
return
}

if rv.Kind() != reflect.Map {
panic("only allow flat map data")
}
flatMap(rv, fn, "")
}

func flatMap(rv reflect.Value, fn FlatFunc, parent string) {
for _, key := range rv.MapKeys() {
path := String(key)
if parent != "" {
path = parent + "." + path
}

fv := Indirect(rv.MapIndex(key))
switch fv.Kind() {
case reflect.Map:
flatMap(fv, fn, path)
case reflect.Array, reflect.Slice:
flatSlice(fv, fn, path)
default:
fn(path, fv)
}
}
}

func flatSlice(rv reflect.Value, fn FlatFunc, parent string) {
for i := 0; i < rv.Len(); i++ {
path := parent + "[" + strconv.Itoa(i) + "]"
fv := Indirect(rv.Index(i))

switch fv.Kind() {
case reflect.Map:
flatMap(fv, fn, path)
case reflect.Array, reflect.Slice:
flatSlice(fv, fn, path)
default:
fn(path, fv)
}
}
}
Loading

0 comments on commit 362bdcb

Please sign in to comment.