获取结构字段类型的简单字符串表示形式

huangapple go评论104阅读模式
英文:

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的简单字符串表示,就像在源代码中一样,例如[]intmap[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/typesExprString函数。

这个函数可以处理复杂的类型,比如[]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)
    ...
}

https://golang.org/src/go/types/exprstring.go

答案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 (
    	&quot;bytes&quot;
    	&quot;fmt&quot;
    	&quot;go/ast&quot;
    	&quot;go/parser&quot;
    	&quot;go/printer&quot;
    	&quot;go/token&quot;
    	&quot;log&quot;
    )
    
    func main() {
    	src := `
            package foo
    
        type Thing struct {
        Field1 string
        Field2 []int
        Field3 map[byte]float64
      }`
    
    	fset := token.NewFileSet()
    	f, err := parser.ParseFile(fset, &quot;&quot;, 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(&amp;typeNameBuf, fset, fld.Type)
    		if err != nil {
    			log.Fatalf(&quot;failed printing %s&quot;, err)
    		}
    		fmt.Printf(&quot;field %d has type %q\n&quot;, i, typeNameBuf.String())
    	}
    }

Output:

    field 0 has type &quot;string&quot;
    field 1 has type &quot;[]int&quot;
    field 2 has type &quot;map[byte]float64&quot;

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 = &quot;&quot;
                     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: ).

huangapple
  • 本文由 发表于 2013年11月27日 13:07:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/20234342.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定