英文:
Get a simple string representation of a struct field’s type
问题
使用Go的ast包,我正在遍历一个结构体的字段列表,代码如下:
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}
// typ是表示上述结构体的*ast.StructType
for _, fld := range typ.Fields.List {
// 将fld.Type作为字符串获取出来
}
我想要得到fld.Type
的简单字符串表示,就像在源代码中一样,例如[]int
或map[byte]float64
。
ast包的field type属性是一个Expr
,所以我发现自己陷入了使用类型切换和具体处理每种类型的困境中,而我唯一的目标是获取每个字段名称右侧的纯字符串,这似乎应该更简单。
有没有简单的方法?
英文:
Using Go’s ast package, I am looping over a struct’s field list like so:
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}
// typ is a *ast.StructType representing the above
for _, fld := range typ.Fields.List {
// get fld.Type as string
}
…and would like to get a simple string representation of fld.Type
, as it appears in the source code, e.g. []int
or map[byte]float64
.
The ast package field type Type property is an Expr
, so I’ve found myself getting off into the weeds using type switches and handling every type specifically – when my only goal is to get out the plain string to the right of each field name, which seems like it should be simpler.
Is there a simple way?
答案1
得分: 20
这里可能有两个问题,一个是关于表达式的类型,在编译过程中最终会解析出来;另一个是决定该类型的代码。
通过查阅文档,我认为第一个问题根本无法解决。然而,你可以通过在Node
上使用End()
和Pos()
来获取后者。
以下是一个快速示例程序:
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
src := `
package foo
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
// 硬编码查找这些内容
typeDecl := f.Decls[0].(*ast.GenDecl)
structDecl := typeDecl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType)
fields := structDecl.Fields.List
for _, field := range fields {
typeExpr := field.Type
start := typeExpr.Pos() - 1
end := typeExpr.End() - 1
// 在源代码中获取类型
typeInSource := src[start:end]
fmt.Println(typeInSource)
}
}
这将输出:
string
[]int
map[byte]float64
如果你想尝试一下,我在golang playground上准备了这个示例。
英文:
There are two things you could be getting at here, one is the type of an expression as would ultimately be resolved during compilation and the other is the code which would determine that type.
Digging through the docs, I don't believe the first is at all available. You can get at the later, however, by using End()
and Pos()
on Node
.
Quick example program:
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
src := `
package foo
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
// hard coding looking these up
typeDecl := f.Decls[0].(*ast.GenDecl)
structDecl := typeDecl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType)
fields := structDecl.Fields.List
for _, field := range fields {
typeExpr := field.Type
start := typeExpr.Pos() - 1
end := typeExpr.End() - 1
// grab it in source
typeInSource := src[start:end]
fmt.Println(typeInSource)
}
}
This prints:
string
[]int
map[byte]float64
I through this together in the golang playground, if you want to mess with it.
答案2
得分: 8
你可以使用go/types
的ExprString
函数。
这个函数可以处理复杂的类型,比如[]string
、[]map[string]string
等。
import (
...
"go/types"
...
)
...
// typ是一个表示上述类型的*ast.StructType
for _, fld := range typ.Fields.List {
...
typeExpr := fld.Type
typeString := types.ExprString(typeExpr)
...
}
https://golang.org/src/go/types/exprstring.go
英文:
You can use go/types
ExprString
This works with complicated types like []string
, []map[string]string
, etc.
import (
...
"go/types"
...
)
...
// typ is a *ast.StructType representing the above
for _, fld := range typ.Fields.List {
...
typeExpr := fld.Type
typeString := types.ExprString(typeExpr)
...
}
答案3
得分: 6
这正是go/printer
包中的Fprint
函数的用途。它以任何AST节点作为参数,并将其字符串表示写入io.Writer
。
你可以按照以下方式在你的示例中使用它:
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"log"
)
func main() {
src := `
package foo
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
typeDecl := f.Decls[0].(*ast.GenDecl)
structDecl := typeDecl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType)
for i, fld := range structDecl.Fields.List {
// get fld.Type as string
var typeNameBuf bytes.Buffer
err := printer.Fprint(&typeNameBuf, fset, fld.Type)
if err != nil {
log.Fatalf("failed printing %s", err)
}
fmt.Printf("field %d has type %q\n", i, typeNameBuf.String())
}
}
输出:
field 0 has type "string"
field 1 has type "[]int"
field 2 has type "map[byte]float64"
在 playground 中尝试:https://play.golang.org/p/cyrCLt_JEzQ
<details>
<summary>英文:</summary>
This is exactly what [Fprint][1] in the `go/printer` package is for. It takes any AST node as an argument and writes its string representation to a `io.Writer`.
You can use it in your example as follows:
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"log"
)
func main() {
src := `
package foo
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
typeDecl := f.Decls[0].(*ast.GenDecl)
structDecl := typeDecl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType)
for i, fld := range structDecl.Fields.List {
// get fld.Type as string
var typeNameBuf bytes.Buffer
err := printer.Fprint(&typeNameBuf, fset, fld.Type)
if err != nil {
log.Fatalf("failed printing %s", err)
}
fmt.Printf("field %d has type %q\n", i, typeNameBuf.String())
}
}
Output:
field 0 has type "string"
field 1 has type "[]int"
field 2 has type "map[byte]float64"
Try it in playground: [https://play.golang.org/p/cyrCLt_JEzQ][2]
[1]: https://golang.org/pkg/go/printer/#Fprint
[2]: https://play.golang.org/p/cyrCLt_JEzQ
</details>
# 答案4
**得分**: 2
我找到了一种方法,可以在不使用原始源代码作为简单成员(不包括切片、数组或结构体)的参考的情况下完成这个任务:
```go
for _, field := range fields {
switch field.Type.(type) {
case *ast.Ident:
stype := field.Type.(*ast.Ident).Name // 类型作为字符串
tag := ""
if field.Tag != nil {
tag = field.Tag.Value // 标签作为字符串
}
name := field.Names[0].Name // 名称作为字符串
...
case *ast.ArrayType:
// 针对非简单成员,你只需要添加另一个 case 语句(例如:`case *ast.ArrayType: `)
}
}
希望这能帮到你!
英文:
I found a way to do this without using the original source code as a reference for simple members (not slices, arrays or structs):
for _, field := range fields {
switch field.Type.(type) {
case *ast.Ident:
stype := field.Type.(*ast.Ident).Name // The type as a string
tag = ""
if field.Tag != nil {
tag = field.Tag.Value //the tag as a string
}
name := field.Names[0].Name //name as a string
...
For the non-simple members you just need another case statement (IE: case *ast.ArrayType:
).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论