You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ntool/nreflect/util.go

252 lines
6.2 KiB
Go

1 year ago
package nreflect
import (
"fmt"
"reflect"
"strconv"
"unsafe"
)
// Elem returns the value that the interface v contains
// or that the pointer v points to.
func Elem(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
return v.Elem()
}
// otherwise, will return self
return v
}
// Indirect like reflect.Indirect(), but can also indirect reflect.Interface
func Indirect(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
return v.Elem()
}
// otherwise, will return self
return v
}
// IndirectAddr walks down v allocating pointers as needed,
// until it gets to a non-pointer.
func IndirectAddr(v reflect.Value) reflect.Value {
v0 := v
haveAddr := false
// 如果v是命名类型并且可寻址(非指针)
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
haveAddr = true
v = v.Addr()
}
for {
// 从接口加载值,但前提是结果可有效寻址。
if v.Kind() == reflect.Interface && !v.IsNil() {
e := v.Elem()
if e.Kind() == reflect.Ptr && !e.IsNil() && e.Elem().Kind() == reflect.Ptr {
haveAddr = false
v = e
continue
}
}
if v.Kind() != reflect.Ptr {
break
}
// 防止无限循环v有可能是是指向其自身地址的接口:
// var v any
// v = &v
if v.Elem().Kind() == reflect.Interface && v.Elem().Elem() == v {
v = v.Elem()
break
}
if v.IsNil() && v.CanSet() {
v.Set(reflect.New(v.Type().Elem()))
}
if haveAddr {
v = v0 // 返回原始值,如果可寻址
haveAddr = false
} else {
v = v.Elem()
}
}
return v
}
1 year ago
// Len get reflect value length
func Len(v reflect.Value) int {
v = reflect.Indirect(v)
// (u)int use width.
switch v.Kind() {
case reflect.String:
return len([]rune(v.String()))
case reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
return v.Len()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return len(strconv.FormatInt(int64(v.Uint()), 10))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return len(strconv.FormatInt(v.Int(), 10))
case reflect.Float32, reflect.Float64:
return len(fmt.Sprint(v.Interface()))
}
// cannot get length
return -1
}
// SliceSubKind get sub-elem kind of the array, slice, variadic-var. alias SliceElemKind()
func SliceSubKind(typ reflect.Type) reflect.Kind {
return SliceElemKind(typ)
}
// SliceElemKind get sub-elem kind of the array, slice, variadic-var.
//
// Usage:
//
// SliceElemKind(reflect.TypeOf([]string{"abc"})) // reflect.String
func SliceElemKind(typ reflect.Type) reflect.Kind {
if typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array {
return typ.Elem().Kind()
}
return reflect.Invalid
}
// UnexportedValue quickly get unexported value by reflect.Value
//
// NOTE: this method is unsafe, use it carefully.
// should ensure rv is addressable by field.CanAddr()
//
// refer: https://stackoverflow.com/questions/42664837/how-to-access-unexported-struct-fields
func UnexportedValue(rv reflect.Value) any {
if rv.CanAddr() {
// create new value from addr, now can be read and set.
return reflect.NewAt(rv.Type(), unsafe.Pointer(rv.UnsafeAddr())).Elem().Interface()
}
// If the rv is not addressable this trick won't work, but you can create an addressable copy like this
rs2 := reflect.New(rv.Type()).Elem()
rs2.Set(rv)
rv = rs2.Field(0)
rv = reflect.NewAt(rv.Type(), unsafe.Pointer(rv.UnsafeAddr())).Elem()
// Now rv can be read. TIP: Setting will succeed but only affects the temporary copy.
return rv.Interface()
}
// SetUnexportedValue quickly set unexported field value by reflect
//
// NOTE: this method is unsafe, use it carefully.
// should ensure rv is addressable by field.CanAddr()
func SetUnexportedValue(rv reflect.Value, value any) {
reflect.NewAt(rv.Type(), unsafe.Pointer(rv.UnsafeAddr())).Elem().Set(reflect.ValueOf(value))
}
// SetValue to a `reflect.Value`. will auto convert type if needed.
func SetValue(rv reflect.Value, val any) error {
// get real type of the ptr value
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
elemTyp := rv.Type().Elem()
rv.Set(reflect.New(elemTyp))
}
// use elem for set value
rv = reflect.Indirect(rv)
}
rv1, err := ValueByType(val, rv.Type())
if err == nil {
rv.Set(rv1)
}
return err
}
// SetRValue to a `reflect.Value`. will direct set value without convert type.
func SetRValue(rv, val reflect.Value) {
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
elemTyp := rv.Type().Elem()
rv.Set(reflect.New(elemTyp))
}
rv = reflect.Indirect(rv)
}
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)
}
}
}