-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move StructLookup to standalone package
- Loading branch information
Showing
5 changed files
with
225 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package reflects | ||
|
||
import ( | ||
"errors" | ||
"reflect" | ||
"sync" | ||
"unsafe" | ||
) | ||
|
||
// StructLookup used to deal reflect operations for struct fields and methods. | ||
// The lookup instance should be reused to improve the performance. | ||
type StructLookup struct { | ||
cache sync.Map //map[reflect.Type]structInfo | ||
} | ||
|
||
// structInfo contains struct meta cache to accelerate the reflect operations. | ||
type structInfo struct { | ||
fields map[string]int // The field name to index map | ||
// The embed fields name to index map. It is a many-to-one map. | ||
// The names is retrieve recursively from all embed type fields; | ||
// The values is the indexes for each level' struct field, from the inner to outer. | ||
embedFields map[string][]int | ||
} | ||
|
||
var defaultStructLookup = sync.OnceValue(func() *StructLookup { | ||
return NewStructLookup() | ||
}) | ||
|
||
// DefaultStructLookup returns the default StructLookup instance. | ||
func DefaultStructLookup() *StructLookup { | ||
return defaultStructLookup() | ||
} | ||
|
||
// NewStructLookup returns a new created StructLookup instance. | ||
func NewStructLookup() *StructLookup { | ||
return &StructLookup{ | ||
cache: sync.Map{}, | ||
} | ||
} | ||
|
||
// Field get the field value for the giving struct value and field name. | ||
// It will look into embed struct fields if no match Field is found. | ||
// | ||
// param v: a reflect contains a struct value, or contains a pointer to struct value. | ||
func (s *StructLookup) Field(v reflect.Value, name string) (reflect.Value, bool) { | ||
if v.Kind() == reflect.Pointer { | ||
v = v.Elem() | ||
} | ||
if v.Kind() != reflect.Struct { | ||
panic(errors.New("not a struct value")) | ||
} | ||
|
||
t := v.Type() | ||
si := s.loadStructFields(t) | ||
if idx, ok := si.fields[name]; ok { | ||
fv := v.Field(idx) | ||
if !fv.CanInterface() { | ||
// try to read unexported fields | ||
p := (*reflectValue)(unsafe.Pointer(&fv)) | ||
p.flag = p.flag & ^flagRO | ||
} | ||
return fv, true | ||
} | ||
|
||
if indexes, ok := si.embedFields[name]; ok { | ||
fv := v | ||
for i := len(indexes) - 1; i >= 0; i-- { | ||
fv = fv.Field(indexes[i]) | ||
if fv.Kind() == reflect.Pointer { | ||
fv = fv.Elem() | ||
} | ||
} | ||
if !fv.CanInterface() { | ||
// try to read unexported fields | ||
p := (*reflectValue)(unsafe.Pointer(&fv)) | ||
p.flag = p.flag & ^flagRO | ||
} | ||
return fv, true | ||
} | ||
|
||
var zero reflect.Value | ||
return zero, false | ||
} | ||
|
||
func (s *StructLookup) loadStructFields(t reflect.Type) structInfo { | ||
v, ok := s.cache.Load(t) | ||
if ok { | ||
return v.(structInfo) | ||
} | ||
|
||
var embeds = map[string][]int{} | ||
var fields = map[string]int{} | ||
for i := 0; i < t.NumField(); i++ { | ||
f := t.Field(i) | ||
if !f.Anonymous { | ||
fields[f.Name] = i | ||
} | ||
} | ||
for i := 0; i < t.NumField(); i++ { | ||
f := t.Field(i) | ||
ft := f.Type | ||
if f.Anonymous { | ||
if ft.Kind() == reflect.Pointer { | ||
ft = ft.Elem() | ||
} | ||
if ft.Kind() == reflect.Struct { | ||
si := s.loadStructFields(ft) | ||
for name, idx := range si.fields { | ||
if _, ok := fields[name]; !ok { | ||
embeds[name] = []int{idx, i} | ||
} | ||
} | ||
for name, indexes := range si.embedFields { | ||
if _, ok := fields[name]; !ok { | ||
embeds[name] = append(indexes, i) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
si := structInfo{ | ||
fields: fields, | ||
embedFields: embeds, | ||
} | ||
|
||
s.cache.Store(t, si) | ||
return si | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package reflects | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestStructLookup_GetField(t *testing.T) { | ||
|
||
v := reflect.ValueOf(mockTestStruct()) | ||
lookup := DefaultStructLookup() | ||
name, ok := lookup.Field(v, "Name") | ||
assert.True(t, ok) | ||
assert.Equal(t, "john", name.String()) | ||
|
||
age, ok := lookup.Field(v, "age") | ||
assert.True(t, ok) | ||
assert.Equal(t, 18, int(age.Int())) | ||
|
||
male, ok := lookup.Field(v, "male") | ||
assert.True(t, ok) | ||
assert.Equal(t, true, male.Bool()) | ||
|
||
weight, ok := lookup.Field(v, "weight") | ||
assert.True(t, ok) | ||
assert.Equal(t, 10.0, weight.Float()) | ||
|
||
rich, ok := lookup.Field(v, "rich") | ||
assert.True(t, ok) | ||
assert.Equal(t, true, rich.Bool()) | ||
} | ||
|
||
func mockTestStruct() any { | ||
return &testStruct{ | ||
Name: "john", | ||
age: 18, | ||
testEmbedStruct2: &testEmbedStruct2{ | ||
weight: 10, | ||
testEmbedStruct21: testEmbedStruct21{ | ||
weight: 11, | ||
rich: true, | ||
}, | ||
}, | ||
testEmbedStruct: testEmbedStruct{ | ||
male: true, | ||
}, | ||
} | ||
} | ||
|
||
type testStruct struct { | ||
testEmbedStruct | ||
*testEmbedStruct2 | ||
Name string | ||
age int | ||
} | ||
|
||
type testEmbedStruct struct { | ||
male bool | ||
} | ||
|
||
type testEmbedStruct2 struct { | ||
weight float32 | ||
testEmbedStruct21 | ||
} | ||
|
||
type testEmbedStruct21 struct { | ||
weight float32 | ||
rich bool | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package reflects | ||
|
||
import "unsafe" | ||
|
||
type flag uintptr | ||
|
||
const ( | ||
flagStickyRO flag = 1 << 5 // unexported not embedded field | ||
flagEmbedRO flag = 1 << 6 // unexported embedded field | ||
flagRO flag = flagStickyRO | flagEmbedRO | ||
) | ||
|
||
type reflectValue struct { | ||
typ_ unsafe.Pointer | ||
ptr unsafe.Pointer | ||
flag | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.