How to create a golang struct with given name of string - go

I am using protobuf to generate a lot of struct, and I want to use gorm.io to Db.AutoMigrate all the struct in the generate package into the database. But the function func (*gorm.DB).AutoMigrate(dst ...interface{}) error can only accept variadic struct instance as its parameters. The following code can retrieved all the name of structs is string format.
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
// src is the input for which we want to inspect the AST.
src := `
package service
type Screen struct {
Width float64
Height float64
}
type Memory struct {
Unit float64
}
`
// Create the AST by parsing src.
fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseFile(fset, "src.go", src, 0)
if err != nil {
panic(err)
}
for _, node := range f.Decls {
switch node.(type) {
case *ast.GenDecl:
genDecl := node.(*ast.GenDecl)
for _, spec := range genDecl.Specs {
switch spec.(type) {
case *ast.TypeSpec:
typeSpec := spec.(*ast.TypeSpec)
fmt.Printf("Struct: name=%s\n", typeSpec.Name.Name)
}
}
}
}
}
My Question is:
This code can got all the name of structs , but the gorm.io need initialize the struct DB.AutoMigrate(&User{}). How can I do create a empty struct that specified by the string of the name at the runtime. eg. the "Screen" ===> &Screen{}.

Related

Reading and Unmarshalling API results in Golang

In the below program I'm extracting some data from an API.
It outputs a rather complex data.
When I ioutil.ReadAll(resp.Body), the result is of type []uint8.
If I try to read the results, its just a random array of integers.
However, I'm able to read it if I convert it to string using string(diskinfo)
But I want to use this in a Struct and having trouble unmarshalling.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
)
type ApiResults struct {
results []struct {
statement_id int `json.statement_id`
series []struct {
name string `json.name`
tags struct {
host string `json.host`
}
columns []string `json.columns`
values []interface{} `json.values`
}
}
}
func main() {
my_url := "my_url"
my_qry := fmt.Sprintf("my_query")
resp, err := http.Get(my_url + url.QueryEscape(my_qry))
if err != nil {
fmt.Printf("ERROR: %v\n", err)
} else {
fmt.Println(reflect.TypeOf(resp))
diskinfo, _ := ioutil.ReadAll(resp.Body)
fmt.Println(reflect.TypeOf((diskinfo)))
fmt.Println(diskinfo)
fmt.Println(string(diskinfo))
diskinfo_string := string(diskinfo)
data := ApiResults{}
json.Unmarshal([]byte(diskinfo_string), &data)
//fmt.Printf("Values = %v\n", data.results.series.values)
//fmt.Printf("Server = %v\n", data.results.series.tags.host)
}
}
If I view the data as a string, I get this (formatted):
{"results":[
{"statement_id":0,
"series":[
{"name":"disk",
"tags":{"host":"myServer1"},
"columns":["time","disk_size"],
"values":[["2021-07-07T07:53:32.291490387Z",1044]]},
{"name":"disk",
"tags":{"host":"myServer2"},
"columns":["time","disk_size"],
"values":[["2021-07-07T07:53:32.291490387Z",1046]]}
]}
]}
I think my Apireturn struct is also structured incorrectly because the API results have info for multiple hosts.
But first, I doubt if the data has to be sent in a different format to the struct. Once I do this, I can probably try to figure out how to read from the Struct next.
The ioutil.ReadAll already provides you the data in the type byte[]. Therefore you can just call json.Unmarshal passing it as a parameter.
import (
"encoding/json"
"io/ioutil"
"net/http"
)
func toStruct(res *http.Response) (*ApiResults, error) {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
defer res.Body.Close()
data := ApiResults{}
if err := json.Unmarshal(body, &data); err != nil {
return nil, err
}
return data, nil
}
There also seems to be an issue with your struct. The correct way to use struct tags is as follows. Plus, fields need to be exported for the json tag (used by json.Umarshal) to work – starting with uppercase will do it.
type ApiResults struct {
Results []struct {
StatementId int `json:"statement_id"`
Series []struct {
Name string `json:"name"`
Tags struct {
Host string `json:"host"`
} `json:"tags"`
Columns []string `json:"columns"`
Values []interface{} `json:"values"`
} `json:"series"`
} `json:"results"`
}

Is possible to reflect an struct from ast

I trying to implement some way to get the reflect.TypeOf() from a *ast.TypeSpec, to work with the struct without needs to import it in the code (I will explain later). For now I have this project structure:
.
├─ main.go
└─ entities
├─ costumer.go
└─ person.go
Files:
// entities/costumer.go
package entities
import "time"
type Costumer struct {
PersonId int
S *int
CreatedAt time.Time
UpdatedAt *time.Time
Goods []struct {
Name string
GoodsId int
}
Goods2
}
func (*Costumer) TableName() string {
return "CustomName"
}
type Goods2 struct {
Name string
Goods2Id int
}
// entities/person.go
package entities
type Person struct {
Id int
Name string
Age int
}
// main.go
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"log"
)
// Main aa
func main() {
// Create the AST by parsing src.
fset := token.NewFileSet() // positions are relative to fset
packages, err := parser.ParseDir(fset, "./entities", nil, 0)
if err != nil {
panic(err)
}
for _, pack := range packages {
for _, file := range pack.Files {
// Inspect the AST and print all identifiers and literals.
ast.Inspect(file, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.TypeSpec: // Gets Type assertions
fmt.Println(x.Name.Name)
v := x.Type.(*ast.StructType)
for _, field := range v.Fields.List {
for _, name := range field.Names {
// get field.Type as string
var typeNameBuf bytes.Buffer
err := printer.Fprint(&typeNameBuf, fset, field.Type)
if err != nil {
log.Fatalf("failed printing %s", err)
}
fmt.Printf("field %+v has type %+v\n", name.Name, typeNameBuf.String())
}
}
fmt.Println()
}
return true
})
}
}
}
And I need some way to get Costumer, Goods2 and Person structs to use in the code. For now I want specifically call the Costumer.TableName method and receive the result of it.
I can't import the package because later it will be a CLI and will recive just the folder to parse/inspect (parser.ParseDir(fset, "<folder goes here>", nil, 0))
So any ideas, suggestions or tips?
A long time passed and I find a way:
After you parsed a AST file and get the structs from package, you could use reflection to create a struct in runtime with the following:
t := reflect.StructOf([]reflect.StructField{
{
Name: "A",
Type: reflect.TypeOf(int(0)),
Tag: `json:"a"`,
},
{
Name: "B",
Type: reflect.TypeOf(""),
Tag: `json:"B"`,
},
// Other fields ...
})
d := reflect.New(t).Interface() // Here you recived a struct as interface. And that's it.
Is possible to reflect an struct from ast in Go[...]?
No, you must redesign.

How to find full package import from CallExpr

The following method extracts all public method calls from the AST of a file. I need to find out the full package from the CallExpr, for example: ast.Inspect() is imported from "go/ast". I want to match the list of pkgsInclude strings with the imported package name:
func functionCalls(path string, node *ast.File, pkgsInclude []string) int {
fCalls := 0
ast.Inspect(node, func(n ast.Node) bool {
switch fCall := n.(type) {
case *ast.CallExpr:
if fun, ok := fCall.Fun.(*ast.SelectorExpr); ok {
if fun.Sel.IsExported() {
fCalls += 1
}
}
}
return true
})
return fCalls
}
To get fully qualified names, the code has to be type checked with the go/types package.
The go/types article by Alan Donovan goes into great detail on how to use the type checker properly, but here is the gist of it. I left a few type assertions in the Visit method for brevity. In production code you shouldn't assume specific node types.
package main
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
)
// code to parse. It includes two variants of calling a package function.
var code = `package main
import (
foo "io/ioutil"
. "io/ioutil"
)
func main() {
foo.ReadFile("")
ReadFile("")
}
`
func main() {
fset := &token.FileSet{}
f, err := parser.ParseFile(fset, "", code, 0)
if err != nil {
log.Fatal(err)
}
// info.Uses allows to lookup import paths for identifiers.
info := &types.Info{
Uses: make(map[*ast.Ident]types.Object),
}
// Type check the parsed code using the default importer.
// Use golang.org/x/tools/go/loader to check a program
// consisting of multiple packages.
conf := types.Config{Importer: importer.Default()}
pkg, err := conf.Check("main", fset, []*ast.File{f}, info)
if err != nil {
log.Fatal(err)
}
// Do something with ast, info, and possibly pkg
var _ = pkg
ast.Walk(v{info}, f)
}
type v struct {
info *types.Info
}
func (v v) Visit(node ast.Node) (w ast.Visitor) {
switch node := node.(type) {
case *ast.CallExpr:
// Get some kind of *ast.Ident for the CallExpr that represents the
// package. Then we can look it up in v.info. Where exactly it sits in
// the ast depends on the form of the function call.
switch node := node.Fun.(type) {
case *ast.SelectorExpr: // foo.ReadFile
pkgID := node.X.(*ast.Ident)
fmt.Println(v.info.Uses[pkgID].(*types.PkgName).Imported().Path())
case *ast.Ident: // ReadFile
pkgID := node
fmt.Println(v.info.Uses[pkgID].Pkg().Path())
}
}
return v
}
// Output:
// io/ioutil
// io/ioutil

Package selector with string name

I am trying to figure out a way to access struct from multiple packages with name.
Here is my structure:
collector/package1
collector/package2
..
package1 contains:
package collector
type NewRule struct {
}
..
package2 contains:
package collector
type OldRule struct {
}
....
In my main.go:
import "github.com/mypackage/collector"
sliceOfCollector := []string{"NewRule", "OldRule"}
for _, col := range sliceOfCollector{
// How to use the above collector name `col` to create struct instance.
}
Use reflect.New with struct type. In Go you have to use type to create a new instance dynamically not string.
Example: To create struct instance dynamically, you can do
package main
import "reflect"
import (
"github.com/collector/package1"
"github.com/collector/package2"
)
func main() {
sliceOfCollector := make([]reflect.Type, 0)
sliceOfCollector = append(sliceOfCollector, reflect.TypeOf((*package1.NewRule)(nil)).Elem()})
sliceOfCollector = append(sliceOfCollector, reflect.TypeOf((*package2.OldRule)(nil)).Elem()})
for _, collectorType := range slice sliceOfCollector {
col := reflect.New(collectorType)
fmt.Printf("%#v\n", col)
}
}
You can use type assertions after that col.Interface().(*package1.NewRule)
EDIT:
After comment interaction, added following.
Creating a instance using factory method. Just an idea.
func main() {
sliceOfCollector := []string{"NewRule", "OldRule"}
for _, col := range sliceOfCollector {
rule := CreateRuleByName(col)
fmt.Printf("%#v\n", rule)
}
}
func CreateRuleByName(name string) interface{} {
switch name {
case "NewRule":
return &package1.NewRule{}
case "OldRule":
return &package2.OldRule{}
default:
return nil
}
}

how to access deeply nested json keys and values

I'm writing a websocket client in Go. I'm receiving the following JSON from the server:
{"args":[{"time":"2013-05-21 16:57:17"}],"name":"send:time"}
I'm trying to access the time parameter, but just can't grasp how to reach deep into an interface type:
package main;
import "encoding/json"
import "log"
func main() {
msg := `{"args":[{"time":"2013-05-21 16:56:16", "tzs":[{"name":"GMT"}]}],"name":"send:time"}`
u := map[string]interface{}{}
err := json.Unmarshal([]byte(msg), &u)
if err != nil {
panic(err)
}
args := u["args"]
log.Println( args[0]["time"] ) // invalid notation...
}
Which obviously errors, since the notation is not right:
invalid operation: args[0] (index of type interface {})
I just can't find a way to dig into the map to grab deeply nested keys and values.
Once I can get over grabbing dynamic values, I'd like to declare these messages. How would I write a type struct to represent such complex data structs?
You may like to consider the package github.com/bitly/go-simplejson
See the doc: http://godoc.org/github.com/bitly/go-simplejson
Example:
time, err := json.Get("args").GetIndex(0).String("time")
if err != nil {
panic(err)
}
log.Println(time)
The interface{} part of the map[string]interface{} you decode into will match the type of that field. So in this case:
args.([]interface{})[0].(map[string]interface{})["time"].(string)
should return "2013-05-21 16:56:16"
However, if you know the structure of the JSON, you should try defining a struct that matches that structure and unmarshal into that. Ex:
type Time struct {
Time time.Time `json:"time"`
Timezone []TZStruct `json:"tzs"` // obv. you need to define TZStruct as well
Name string `json:"name"`
}
type TimeResponse struct {
Args []Time `json:"args"`
}
var t TimeResponse
json.Unmarshal(msg, &t)
That may not be perfect, but should give you the idea
I'm extremely new to Golang coming from Python, and have always struggled with encode/decoding json. I found gjson at https://github.com/tidwall/gjson, and it helped me immensely:
package main
import "github.com/tidwall/gjson"
func main() {
msg := (`{"args":[{"time":"2013-05-21 16:56:16", "tzs":[{"name":"GMT"}]}],"name":"send:time"}`)
value := gjson.Get(msg, "args.#.time")
println(value.String())
}
-----------------------
["2013-05-21 16:56:16"]
Additionally, I noticed the comment of how to convert into Struct
package main
import (
"encoding/json"
"fmt"
)
type msgFormat struct {
Time string `json:"time"`
Tzs msgFormatTzs `json:"tzs"`
Name string `json:"name"`
}
type msgFormatTzs struct {
TzsName string `json:"name"`
}
func main() {
msg := (`{"args":[{"time":"2013-05-21 16:56:16", "tzs":[{"name":"GMT"}]}],"name":"send:time"}`)
r, err := json.Marshal(msgFormatTzs{msg})
if err != nil {
panic(err)
}
fmt.Printf("%v", r)
}
Try on Go playground

Resources