Modify array of interface{} golang - go

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

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

Go: A function that would consume maps with different types of values

In my code, I need a function that would return an ordered slice of keys from a map.
m1 := make(map[string]string)
m2 := make(map[string]int)
And now I need to call a function passing both types of maps:
keys1 := sortedKeys(m1)
keys2 := sortedKeys(m1)
Problem: I have to write two functions because the function should consume maps of two different types. At the same time, the body of the function will be the same in both cases.
Question: How can I use a single implementation for two maps? Or is there any other way of solving the problem in an elegant way?
My first idea was to use map[string]interface{} as an argument type, but you can't assign neither map[string]string, nor map[string]int to it.
My code:
func sortedKeys(m map[string]string) []string {
var keys []string
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
I would have to repeat the same code but for map[string]int.
You can use interface{} and use reflection for achieving this.
You can write two functions for the same but it is just not scalable, say, you are supporting string and int now but you wish to support int64, float64, bool or struct in the future. Having a common function using map[string]interface{} and using reflection is the way to go.
Suggested Code :
package main
import (
"fmt"
"reflect"
)
func main() {
m1 := make(map[string]string)
m2 := make(map[string]int)
m1["a"] = "b"
m1["b"] = "c"
m2["a"] = 1
m2["b"] = 2
fmt.Println(sortedKeys(m1))
fmt.Println(sortedKeys(m2))
}
// Returns slice of values in the type which is sent to it
func sortedKeys(m interface{}) interface{} {
if m == nil {
return nil
}
if reflect.TypeOf(m).Kind() != reflect.Map {
return nil
}
mapIter := reflect.ValueOf(m).MapRange()
mapVal := reflect.ValueOf(m).Interface()
typ := reflect.TypeOf(mapVal).Elem()
outputSlice := reflect.MakeSlice(reflect.SliceOf(typ), 0, 0)
for mapIter.Next() {
outputSlice = reflect.Append(outputSlice, mapIter.Value())
}
return outputSlice.Interface()
}
Output :
[b c]
[1 2]
https://play.golang.org/p/2fkpydH9idG

Passing slices to a function

I have some confusion regarding passign slices to function. Here is what I have readed:
Here are what I have understood: slice is a structure with a pointer to real data; when we are passing a slice to a function, we just copy a pointer, but the function is working with the same data as original function.
Here is my code:
type Example struct {
A int
B string
}
func foo(d []Example) {
for _, e := range d {
e.B = "bye"
}
}
func main() {
a := Example{}
a.A = 10
a.B = "hello"
b := Example{}
b.A = 10
b.B = "hello"
var c []Example
c = append(c, a)
c = append(c, b)
foo(c)
for _, e := range c {
fmt.Println(e.B)
}
}
I have passed slice of structs to a function and have changed the struct in the function. Why I have old values in the main function ?
Because it's a slice of structs, not a slice of pointers to structs. When you execute:
for _, e := range d
Inside the loop, e is a copy of the element from the slice; modifying it does not modify what's in the slice. If d were a []*Example, it would work as you expected: https://play.golang.org/p/4ZgLETpq6d0
Note in particular that this has nothing at all to do with slices. If it were:
func foo(d Example) {
d.B = "bye"
}
You would run into the same problem: the function is modifying a copy of the struct, so the caller's copy is unaffected by what happens inside the function.
Another potential solution without using pointers would be to modify the values inside the slice, rather than in a copy of the element:
func foo(d []Example) {
for i := range d {
d[i].B = "bye"
}
}
Working example of this style: https://play.golang.org/p/_UJGU0XqaUO

Getting substruct field in Go

I am trying to get fields from a struct value using reflection.
package main
import (
"fmt"
"reflect"
)
type Vertex struct {
X string
Y string
SubVertex SubVertex
}
type SubVertex struct {
Z string
}
func get_field(v Vertex, field string) string {
r := reflect.ValueOf(v)
f := reflect.Indirect(r).FieldByName(field)
return f.String()
}
func main() {
v := Vertex{"a", "b", SubVertex{"c"}}
fmt.Println(get_field(v, "X"))
fmt.Println(get_field(v, "Y"))
fmt.Println(get_field(v, "Z")) // Invalid Value
}
I get Invalid Value in the third case, when I try to get the value of the Z field. If SubVertex were an anonymous field, this would work, but I need to use a named field.
How do I make this work?
In this case, you have to use the reflect package in the same manner as you would accessing the values normally. So
v.X // a
v.Y // b
v.SubVertex.Z // c
becomes
r := reflect.ValueOf(v)
x := reflect.Indirect(r).FieldByName("X")
x.String() // a
...
z := reflect.Indirect(r).FieldByName("SubVertex").FieldByName("Z")
z.String() // c
Note that FieldByName() is called on a Value and returns a Value, so it works much the same as just accessing it regularly. Also note that as per the documentation:
Indirect returns the value that v points to. If v is a nil pointer, Indirect returns a zero Value. If v is not a pointer, Indirect returns v.
So the call to Indirect() would be a No-op, but would protect it from having a meltdown if you decided to give it a pointer in the future.
As for your function, this would work
func get_field(v Vertex, field string) string {
r := reflect.ValueOf(v)
if field == "Z" {
f := reflect.Indirect(r).FieldByName("SubVertex").FieldByName(field)
return f.String()
}
f := reflect.Indirect(r).FieldByName(field)
return f.String()
}
https://play.golang.org/p/eZyTl8OSTZ

Initialize structs from list of strings

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.

Resources