英文:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论