Get a simple string representation of a struct field’s type

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.


This is exactly what Fprint 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


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