Initialize structs from list of strings - go

I'm trying to initialize structs from a list of strings, but the compiler is throwing the following error. I'm still learning the language so excuse my ignorance, but is this solved by utilizing type assertion?
ERROR: v.UberX undefined (type string has no field method UberX)
type Galaxy struct {
UberX int64
UberY int64
}
func main() {
galaxies := []string{"andromeda", "milkyway", "maffei"}
for _, v := range galaxies {
v := &Galaxy{}
}
for _, v := range galaxies {
v.UberX += 1000
v.UberY += 750
}
}

Your Galaxy struct doesn't even store the name, in your attempt there isn't any connection between the names and the struct values. Add the name to the struct:
type Galaxy struct {
Name string
UberX int64
UberY int64
}
Next, in your first loop you create a *Galaxy value, but you only store it in a local variable v which by the way shadows the loop variable v:
for _, v := range galaxies {
v := &Galaxy{}
}
You need a slice of Galaxy or a slice of *Galaxy which you can populate:
gs := make([]*Galaxy, len(galaxies))
Then 1 loop is enough to loop over the galaxy names and populate the gs slice:
for i, v := range galaxies {
gs[i] = &Galaxy{
Name: v,
UberX: 1000,
UberY: 750,
}
}
Verifying the result:
for _, v := range gs {
fmt.Printf("%+v\n", v)
}
Output (try it on the Go Playground):
&{Name:andromeda UberX:1000 UberY:750}
&{Name:milkyway UberX:1000 UberY:750}
&{Name:maffei UberX:1000 UberY:750}
Recommended to go through the Golang Tour first to learn the basics.

Related

How to convert a slice of maps to a slice of structs with different properties

I am working with an api and I need to pass it a slice of structs.
I have a slice of maps so I need to convert it to a slice of structs.
package main
import "fmt"
func main() {
a := []map[string]interface{}{}
b := make(map[string]interface{})
c := make(map[string]interface{})
b["Prop1"] = "Foo"
b["Prop2"] = "Bar"
a = append(a, b)
c["Prop3"] = "Baz"
c["Prop4"] = "Foobar"
a = append(a, c)
fmt.Println(a)
}
[map[Prop1:Foo Prop2:Bar] map[Prop3:Baz Prop4:Foobar]]
so in this example, I have the slice of maps a, which contains b and c which are maps of strings with different keys.
I'm looking to convert a to a slice of structs where the first element is a struct with Prop1 and Prop2 as properties, and where the second element is a struct with Prop3 and Prop4 as properties.
Is this possible?
I've looked at https://github.com/mitchellh/mapstructure but I wasn't able to get it working for my use case. I've looked at this answer:
https://stackoverflow.com/a/26746461/3390419
which explains how to use the library:
mapstructure.Decode(myData, &result)
however this seems to assume that the struct of which result is an instance is predefined, whereas in my case the structure is dynamic.
What you can do is to first loop over each map individually, using the key-value pairs of each map you construct a corresponding slice of reflect.StructField values. Once you have such a slice ready you can pass it to reflect.StructOf, that will return a reflect.Type value that represents the dynamic struct type, you can then pass that to reflect.New to create a reflect.Value which will represent an instance of the dynamic struct (actually pointer to the struct).
E.g.
var result []any
for _, m := range a {
fields := make([]reflect.StructField, 0, len(m))
for k, v := range m {
f := reflect.StructField{
Name: k,
Type: reflect.TypeOf(v), // allow for other types, not just strings
}
fields = append(fields, f)
}
st := reflect.StructOf(fields) // new struct type
sv := reflect.New(st) // new struct value
for k, v := range m {
sv.Elem(). // dereference struct pointer
FieldByName(k). // get the relevant field
Set(reflect.ValueOf(v)) // set the value of the field
}
result = append(result, sv.Interface())
}
https://go.dev/play/p/NzHQzKwhwLH

Better way to store references in a map

What would be a better way than this to store references to structs in a map? Right now I'm using an anonymous function so that all the map keys don't end up with the same reference. I'm sure there has to be a more elegant way to do it.
m := make(map[string]*Result)
for result := range results {
func(r Result) {
m[r.Key] = &r
}(result)
}
Update: results is a channel of simple structs
Use the following to store a pointer to a newly allocated value:
m := make(map[string]*Result)
for r := range results {
r := r
m[r.Key] := &r
}
If you're trying to copy the result struct:
m := make(map[string]*Result)
for result := range results {
result:=result
m[result.Key] = &result
}

Appending to struct slice in Go

I have two structs, like so:
// init a struct for a single item
type Cluster struct {
Name string
Path string
}
// init a grouping struct
type Clusters struct {
Cluster []Cluster
}
What I want to do is append to new items to the clusters struct. So I wrote a method, like so:
func (c *Clusters) AddItem(item Cluster) []Cluster {
c.Cluster = append(c.Cluster, item)
return c.Cluster
}
The way my app works, I loop through some directories then append the name of the final directory and it's path. I have a function, that is called:
func getClusters(searchDir string) Clusters {
fileList := make([]string, 0)
//clusterName := make([]string, 0)
//pathName := make([]string, 0)
e := filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error {
fileList = append(fileList, path)
return err
})
if e != nil {
log.Fatal("Error building cluster list: ", e)
}
for _, file := range fileList {
splitFile := strings.Split(file, "/")
// get the filename
fileName := splitFile[len(splitFile)-1]
if fileName == "cluster.jsonnet" {
entry := Cluster{Name: splitFile[len(splitFile)-2], Path: strings.Join(splitFile[:len(splitFile)-1], "/")}
c.AddItem(entry)
}
}
Cluster := []Cluster{}
c := Clusters{Cluster}
return c
}
The problem here is that I don't know the correct way to do this.
Currently, I'm getting:
cmd/directories.go:41:4: undefined: c
So I tried moving this:
Cluster := []Cluster{}
c := Clusters{Cluster}
Above the for loop - range. The error I get is:
cmd/directories.go:43:20: Cluster is not a type
What am I doing wrong here?
The error is in the loop where you are calling AddItem function on Cluster method receiver which is not defined inside getClusters function. Define Cluster struct before for loop and then call the function c.AddItem as defined below:
func getClusters(searchDir string) Clusters {
fileList := make([]string, 0)
fileList = append(fileList, "f1", "f2", "f3")
ClusterData := []Cluster{}
c := Clusters{Cluster: ClusterData} // change the struct name passed to Clusters struct
for _, file := range fileList {
entry := Cluster{Name: "name" + file, Path: "path" + file}
c.AddItem(entry)
}
return c
}
you have defined the same struct name to Clusters struct that's why the error
cmd/directories.go:43:20: Cluster is not a type
Checkout working code on Go playground
In Golang Composite literal is defined as:
Composite literals construct values for structs, arrays, slices, and maps and create a new value each time they are evaluated. They
consist of the type of the literal followed by a brace-bound list of
elements. Each element may optionally be preceded by a corresponding
key.
Also Have a look on struct literals section defined in above link for Compositeliterals to get more description.
You need to define c before entering the loop in which you use it.
The Cluster is not a type error is due to using the same Cluster name as the type and the variable, try using a different variable name.
clusterArr := []Cluster{}
c := Clusters{clusterArr}
for _, file := range fileList {
....
}

Interface for a slice of arbitrary structs to use as a function parameter (golang)

I have two different types of structs in my app.
I'll show it as a simplified example:
type typeA struct {
fieldA1 int
fieldA2 string
}
type typeB struct {
fieldB1 float32
fieldB2 bool
}
First I init slices of them, then I want to store them in DB.
a := []typeA{
{10, "foo"},
{20, "boo"},
}
b := []typeB{
{2.5, true},
{3.5, false},
}
My first attempt was to iterate over first slice, then over second slice. It works just fine, but doesn't look DRY. The code is clearly duplicated:
printBothArrays(a, b)
// ...
func printBothArrays(dataA []typeA, dataB []typeB) {
// Not DRY
for i, row := range dataA {
fmt.Printf("printBothArrays A row %d: %v\n", i, row)
}
for i, row := range dataB {
fmt.Printf("printBothArrays B row %d: %v\n", i, row)
}
}
A wrong way to make it DRY is to split it into 2 functions:
printArrayA(a)
printArrayB(b)
// ...
func printArrayA(data []typeA) {
// Not DRY too, because the code is just split between 2 funcs
for i, row := range data {
fmt.Printf("printArrayA row %d: %v\n", i, row)
}
}
func printArrayB(data []typeB) {
// Not DRY too, because the code is just split between 2 funcs
for i, row := range data {
fmt.Printf("printArrayB row %d: %v\n", i, row)
}
}
These two functions' signatures are different, but the code is just the same!
I thought of an universal function which can take any []struct and just store it. As my store function can take any interface{}, I thought of this:
func printArrayAny(data [](interface{})) {
for i, row := range data {
fmt.Printf("printArrayAny row %d: %v\n", i, row)
}
}
But I've tried different ways and I can't match any shared interface. I'm getting errors like:
cannot use a (type []typeA) as type []interface {} in argument to printArrayAny
I don't really want to make any heavy lifting like converting it to []map[string]interface, or using reflect, as both slices are really big.
Is there a way to modify printArrayAny so it can receive and iterate over any arbitrary []struct ?
Go playground link: https://play.golang.org/p/qHzcQNUtLIX
Use the reflect package to iterate over arbitrary slice types:
func printArrayAny(data interface{}) {
v := reflect.ValueOf(data)
for i := 0; i < v.Len(); i++ {
fmt.Printf("printArrayAny row %d: %v\n", i, v.Index(i).Interface())
}
}
Playground Example.

Modify array of interface{} golang

This type assertion, def-referencing has been driving me crazy. So I have a nested structure of Key string / Value interface{} pairs. Stored in the Value is an []interface which I want to modify each of the values. Below is an example of creating an array of Bar and passing it into the ModifyAndPrint function which should modify the top level structure. The problem that I come accross is as written it doesn't actually modify the contents of z, and I can't do a q := z.([]interface{})[i].(Bar) or & thereof.
Is there a way to do this? If so, what combination did I miss?
package main
import "fmt"
type Bar struct {
Name string
Value int
}
func ModifyAndPrint(z interface{}){
fmt.Printf("z before: %v\n", z)
for i, _ := range(z.([]interface{})) {
q := z.([]interface{})[i]
b := (q).(Bar)
b.Value = 42
fmt.Printf("Changed to: %v\n", b)
}
fmt.Printf("z after: %v\n", z)
}
func main() {
bars := make([]interface{}, 2)
bars[0] = Bar{"a",1}
bars[1] = Bar{"b",2}
ModifyAndPrint(bars)
}
https://play.golang.org/p/vh4QXS51tq
The program is modifying a copy of the value in the interface{}. One way to achieve your goal is to assign the modified value back to the slice:
for i, _ := range(z.([]interface{})) {
q := z.([]interface{})[i]
b := (q).(Bar)
b.Value = 42
z.([]interface{})[i] = b
fmt.Printf("Changed to: %v\n", b)
}
playground example

Resources