Skip to content

Commit a2429ba

Browse files
committed
Add TraversalsByName for zero allocation field traversal
1 parent d9bd385 commit a2429ba

File tree

2 files changed

+94
-6
lines changed

2 files changed

+94
-6
lines changed

reflectx/reflect.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,20 +166,39 @@ func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
166166
// traversals for each mapped name. Panics if t is not a struct or Indirectable
167167
// to a struct. Returns empty int slice for each name not found.
168168
func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
169+
r := make([][]int, 0, len(names))
170+
m.TraversalsByNameFunc(t, names, func(_ int, i []int) error {
171+
if i == nil {
172+
r = append(r, []int{})
173+
} else {
174+
r = append(r, i)
175+
}
176+
177+
return nil
178+
})
179+
return r
180+
}
181+
182+
// TraversalsByNameFunc traverses the mapped names and calls fn with the index of
183+
// each name and the struct traversal represented by that name. Panics if t is not
184+
// a struct or Indirectable to a struct. Returns the first error returned by fn or nil.
185+
func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error {
169186
t = Deref(t)
170187
mustBe(t, reflect.Struct)
171188
tm := m.TypeMap(t)
172-
173-
r := make([][]int, 0, len(names))
174-
for _, name := range names {
189+
for i, name := range names {
175190
fi, ok := tm.Names[name]
176191
if !ok {
177-
r = append(r, []int{})
192+
if err := fn(i, nil); err != nil {
193+
return err
194+
}
178195
} else {
179-
r = append(r, fi.Index)
196+
if err := fn(i, fi.Index); err != nil {
197+
return err
198+
}
180199
}
181200
}
182-
return r
201+
return nil
183202
}
184203

185204
// FieldByIndexes returns a value for the field given by the struct traversal

reflectx/reflect_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,3 +903,72 @@ func BenchmarkFieldByIndexL4(b *testing.B) {
903903
}
904904
}
905905
}
906+
907+
func BenchmarkTraversalsByName(b *testing.B) {
908+
type A struct {
909+
Value int
910+
}
911+
912+
type B struct {
913+
A A
914+
}
915+
916+
type C struct {
917+
B B
918+
}
919+
920+
type D struct {
921+
C C
922+
}
923+
924+
m := NewMapper("")
925+
t := reflect.TypeOf(D{})
926+
names := []string{"C", "B", "A", "Value"}
927+
928+
b.ResetTimer()
929+
930+
for i := 0; i < b.N; i++ {
931+
if l := len(m.TraversalsByName(t, names)); l != len(names) {
932+
b.Errorf("expected %d values, got %d", len(names), l)
933+
}
934+
}
935+
}
936+
937+
func BenchmarkTraversalsByNameFunc(b *testing.B) {
938+
type A struct {
939+
Z int
940+
}
941+
942+
type B struct {
943+
A A
944+
}
945+
946+
type C struct {
947+
B B
948+
}
949+
950+
type D struct {
951+
C C
952+
}
953+
954+
m := NewMapper("")
955+
t := reflect.TypeOf(D{})
956+
names := []string{"C", "B", "A", "Z", "Y"}
957+
958+
b.ResetTimer()
959+
960+
for i := 0; i < b.N; i++ {
961+
var l int
962+
963+
if err := m.TraversalsByNameFunc(t, names, func(_ int, _ []int) error {
964+
l++
965+
return nil
966+
}); err != nil {
967+
b.Errorf("unexpected error %s", err)
968+
}
969+
970+
if l != len(names) {
971+
b.Errorf("expected %d values, got %d", len(names), l)
972+
}
973+
}
974+
}

0 commit comments

Comments
 (0)