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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
}
// 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)
}
}
}