Obtenga una representación de cadena simple del tipo de un campo de estructura

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 Tipo de campo 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.

¿Existe una forma sencilla?

preguntado el 27 de noviembre de 13 a las 05:11

¿Has probado theFileString[fld.Type.Pos():fld.Type.End()]? -

4 Respuestas

There are two things you could be getting at here, one is the tipo of an expression as would ultimately be resolved during compilation and the other is the código 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() y 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)
    }

}

Esto imprime:

string
[]int
map[byte]float64

I through this together in the golang playground, if you want to mess with it.

respondido 27 nov., 13:06

Helpful and also unfortunate. :) The file business is way upstream in my program, gonna have to restructure. - mate sherman

Puedes usar 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

Respondido el 24 de diciembre de 20 a las 03:12

This is a better answer, go/ast stores the Type in the Expr, Utilizar types.ExprString para conseguirlo. - Yami Odimel

Esto es exactamente lo que Fprint en el capítulo respecto a la 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())
    }
}

Salida:

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

Respondido 27 Feb 20, 22:02

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

Respondido 25 Feb 18, 01:02

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.