英文:
How to convert a struct into a flat array of paths?
问题
如果我有一个结构体,例如:
type Example struct {
Foo string
Bar string
Baz struct{
A int
B string
}
Qux []string
}
将其转换为表示每个结构体字段的点路径的字符串切片,最佳方法是什么?
我希望输出的结果看起来像下面的切片:
["Example.Foo", "Example.Bar", "Example.Baz.A", "Example.Baz.B", "Example.Qux.0", "Example.Qux.1"]
确切的结构体在编译时是已知的。此外,从结构体到平面列表的转换在热路径上,因此性能将是一个重要的考虑因素。
任何提示将不胜感激!
英文:
If for example I have a struct such as:
type Example struct {
Foo string
Bar string
Baz struct{
A int
B string
}
Qux []string
}
What would be the best approach to convert it into a flat slice of strings representing the dot path to each struct field?
I want an output that looks like the following slice:
["Example.Foo", "Example.Bar", "Example.Baz.A", "Example.Baz.B", "Example.Qux.0", "Example.Qux.1"]
The exact structs will be known at compile time. Also, the conversion from struct to a flat list is in a hot path so performance will be an important consideration.
Any hints would be appreciated!
答案1
得分: 2
你必须自己编写代码,使用反射来实现。
这是一个演示性的函数,打印出你提供的输出:
package main
import (
"fmt"
"reflect"
"strconv"
)
type Example struct {
Foo string
Bar string
Baz struct {
A int
B string
}
Qux []string
}
func main() {
example := Example{Qux: []string{"a", "b"}}
t := reflect.ValueOf(example)
prefix := t.Type().Name()
fmt.Println(ToPathSlice(t, prefix, make([]string, 0)))
}
func ToPathSlice(t reflect.Value, name string, dst []string) []string {
switch t.Kind() {
case reflect.Ptr, reflect.Interface:
return ToPathSlice(t.Elem(), name, dst)
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
fname := t.Type().Field(i).Name
dst = ToPathSlice(t.Field(i), name+"."+fname, dst)
}
case reflect.Slice, reflect.Array:
for i := 0; i < t.Len(); i++ {
dst = ToPathSlice(t.Index(i), name+"."+strconv.Itoa(i), dst)
}
default:
return append(dst, name)
}
return dst
}
将打印出:
[Example.Foo Example.Bar Example.Baz.A Example.Baz.B Example.Qux.0 Example.Qux.1]
请注意:
- 反射会带来性能损失;如果你关心性能,应该对相关代码路径进行性能分析,看看是否会影响性能。
- 上述代码是人为构造的,例如它不处理映射(maps),不处理
nil等;你可以根据需要进行扩展。 - 在你期望的输出中,会打印出切片/数组字段的索引。切片没有固定的长度,需要使用
reflect.Value来获取长度。我认为这会使代码变得更加笨拙。如果你可以接受不打印切片索引,那么可以使用reflect.Type来处理。
英文:
You have to code it yourself, with reflection.
This is a demonstrative function that prints the output you provided:
package main
import (
"fmt"
"reflect"
"strconv"
)
type Example struct {
Foo string
Bar string
Baz struct{
A int
B string
}
Qux []string
}
func main() {
example := Example{Qux: []string{"a", "b"}}
t := reflect.ValueOf(example)
prefix := t.Type().Name()
fmt.Println(ToPathSlice(t, prefix, make([]string, 0)))
}
func ToPathSlice(t reflect.Value, name string, dst []string) []string {
switch t.Kind() {
case reflect.Ptr, reflect.Interface:
return ToPathSlice(t.Elem(), name, dst)
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
fname := t.Type().Field(i).Name
dst = ToPathSlice(t.Field(i), name+"."+fname, dst)
}
case reflect.Slice, reflect.Array:
for i := 0; i < t.Len(); i++ {
dst = ToPathSlice(t.Index(i), name+"."+strconv.Itoa(i), dst)
}
default:
return append(dst, name)
}
return dst
}
Will print:
[Example.Foo Example.Bar Example.Baz.A Example.Baz.B Example.Qux.0 Example.Qux.1]
Note that:
- reflection comes at a performance penalty; if you are concerned about this, you should profile the relevant code path to see if it's a deal breaker
- the above code is contrived, for example it doesn't handle maps, it doesn't handle
nil, etc.; you can expand it yourself - in your desired output, the indices of slice/array fields is printed. Slices don't have inherent length as arrays. In order to know the length of slices, you have to work with
reflect.Value. This IMO makes the code more awkward. If you can accept not printing indices for slices, then you can work withreflect.Type.
Playground: https://play.golang.org/p/isNFSfFiXOP
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论