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

问题描述:

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, eg ”[]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?

使用Go的 ast包,我像这样遍历结构的字段列表: p>

  type Thing struct {
 Field1 string 
 Field2 [] int  
 Field3 map [byte] float64 
} 
 
 // typ是一个* ast.StructType,代表上述
 for _,fld:=范围typ.Fields.List {
 //得到fld.Type为 字符串
} 
  code>  pre> 
 
 

...,并希望获得fld.Type的简单字符串表示形式,如它在源代码中所示,例如“ [] int” 或“ map [byte] float64”。 p>

ast包 field type Type属性是一个Expr,所以我发现自己使用类型开关进入杂草并专门处理每种类型-当我的唯一目标是找出每个字段右侧的纯字符串时 名称,似乎应该更简单。 p>

有没有简单的方法? p> div >

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.

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:).

The best way to accomplish this that I have found is to use the Fprint method in the go/printer package.

It accepts any AST node as an argument and writes its string representation out to any io.Writer.

You should be able to use it in your example as follows:

for i, fld := range typ.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 name %q", i, typeNameBuf.String())
}