Go nested map iteration - go

I'm trying to iterate over the entire keys of a map eventObj, including the nested objects inside it and check every key and value for further actions.
So, if I see another nested map I will need to iterate it as well.
I've tried to do so with the comparison of the type to map[string]interface or map[string]interface{} but it seems to be a syntax error.
My question is how to identify a nested map?
(note that I can have several nested maps)
func lookForEmailsInEvent(eventObj map[string]interface {}) {
for key, _ := range eventObj {
valueType := reflect.TypeOf(eventObj[key]).String()
fmt.Printf("%v : %v\n", key, valueType)
if valueType == map[string]interface {
lookForEmailsInEvent(eventObj[key])
} else if key == "email" {
// do something...
}
}
}
This is the value of eventObj (screenshot from terminal):

Here's how to recurse through the nested data:
func lookForEmailsInEvent(eventObj map[string]any) {
for k, v := range eventObj {
if v, ok := v.(map[string]any); ok {
lookForEmailsInEvent(v)
} else if k == "email" {
// do something
}
}
}
This code uses a type assertion to determine if a value is a map[string]any.
Type assertions are covered in the Tour of Go.

Related

Golang search specific item inside Struct array

I'm trying to find the best way for searching inside an Stuct array for getting a specific item with the id of the element.
type Device struct {
Addr net.Addr
AssignedId int
Data string
}
type RegistredDevices struct {
AllDevices []Device
}
Right now i do that
var currentDevice models.Device
for _, device := range registredDevices.AllDevices {
if device.AssignedId == id{
currentDevice = device
}
}
I expected to do something better like that for searching, but i don't know what do to if item can't be find. What should be the return ? Can i return nil or i need to return an empty Device ?
func (registerDevice *RegistredDevices) GetById(id int) Device{
for _, device := range registerDevice.AllDevices {
if device.AssignedId == id{
return device
}
else{
return ?????
}
}
}
var currentDevice = registredDevices.GetById(1)
To signal that an item wasn't found you could either return a pointer to an item (which would be nil if not found), or use two return values with an error or a boolean.
For example map lookups return a value, ok pair. Example from the spec:
An index expression on a map a of type map[K]V used in an assignment or initialization of the special form
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
yields an additional untyped boolean value. The value of ok is true if the key x is present in the map, and false otherwise.
In your case it would be:
func (registerDevice *RegistredDevices) GetById(id int) (Device, bool) {
for _, device := range registerDevice.AllDevices {
if device.AssignedId == id {
return device, true
}
}
return Device{}, false
}
And then:
if currentDevice, ok := registredDevices.GetById(1); ok {
// found. use currentDevice
} else {
// not found
}

How to reduce time complexity while mapping structs with sub-slices in go?

Let's say there's the following Unload struct, which comes as a single element response from microservice A, and where each Item initially has an empty Units slice:
type Unload struct {
UnloadCode string
Orders []Order
}
type Order struct {
OrderCode string
Items []Item
}
type Item struct {
ItemCode string
Units []string
}
And also a ItemUnit struct which comes as a slice response from microservice B:
type ItemUnit struct {
ItemCode string
Units []Unit
}
type Unit struct {
UnitName string
}
And we need to populate the Item, Units slice with it's corresponding UnitName values, based on similar ItemCodes on both sides.
I've managed to come up with the following solution, for solving this issue:
for orderIndex, order := range unload.Orders {
for itemIndex, item := range order.Items {
for _, itemUnit := range itemUnits {
if item.ItemCode == itemUnit.ItemCode {
for _, unit := range itemUnit.Units {
unload.Orders[orderIndex].Items[itemIndex].Units = append(unload.Orders[orderIndex].Items[itemIndex].Units, unit.UnitName)
}
}
}
}
}
I'm not a go expert myself, but it seems to me that this solution comes with a very high time complexity cost. Would there be any other, more elegant and possibly with a smaller time complexity, way of solving this issue?
*Keep in mind that I can't change the structure of any of my structs.
First, create a map for ItemUnit where itemUnit.ItemCode as key and slice of UnitName as value
itemUnitmap := make(map[string][]string)
for _, itemUnit := range itemUnits {
var units []string
for _, unit := range itemUnit.Units {
units = append(units, unit.UnitName)
}
itemUnitmap[itemUnit.ItemCode] = units
}
Then use map to get slice of UnitName using item.ItemCode. Add slice into Item.Units using variadic function
for orderIndex, order := range unload.Orders {
for itemIndex, item := range order.Items {
if units, ok := itemUnitmap[item.ItemCode]; ok {
unload.Orders[orderIndex].Items[itemIndex].Units = append(unload.Orders[orderIndex].Items[itemIndex].Units, units...)// variadic function used to append slice into slice
}
}
}

Initialize nested struct map

The problem:
I've got a map of structs in another struct and I'd like to initialize the nested map of structs, but apparently that is not possible.
Code:
type Exporter struct {
TopicsByName map[string]Topic
}
type Topic struct {
Name string
Partitions map[int32]Partition
}
type Partition struct {
PartitionID int32
HighWaterMark int64
}
// Eventually I want to do something like:
e := Exporter{ TopicsByName: make(map[string]Topic) }
for _, topicName := range topicNames {
// This does not work because "cannot assign to struct field e.TopicsByName[topicName].Partitions in map"
e.TopicsByName[topicName].Partitions = make(map[int32]Partition)
}
// I wanted to initialize all these maps so that I can do
e.TopicsByName[x.TopicName].Partitions[x.PartitionID] = Partition{...}
I don't understand why I can not initialize the nested struct map above. Is it so bad to nest maps with struct as values? How can I fix this?
It is not possible to assign to a field in a map value. The fix is to
assign a struct value to the map value:
for _, topicName := range []string{"a"} {
e.TopicsByName[topicName] = Topic{Partitions: make(map[int32]Partition)}
}
You can initialize it like you'd expect:
e := Exporter{
TopicsByName: map[string]Topic{
"my-topic": Topic{
Name: "my-topic",
Partitions: map[int32]Partition{
int32(1): Partition{
PartitionID: int32(1),
HighWaterMark: int64(199),
},
},
},
},
}
This isn't directly answering the question, since you wanted to iterate over a list of topics, but if this is used in kafka testing, I highly recommend the more verbose/literal format above.
https://play.golang.org/p/zm3A7CIvbyu
If you've known an initialed value. Why don't do it like
val := `
{
"topics_by_name": {
"name1": {
"name":"topicname1",
"partions": {
1: {
"partion_id":1,
"high_water_mark":12,
},
2: {}
}
},
"name2": {}
}
}
`
func main() {
var m map[string]interface{}
// if m has be well designed, use your designed map is better
// init
json.Unmarshal(val, &m)
}

Reference type confusing in Go language

I tried to make Trie data structures by Go Language, but somehow it stuck with References problem,
Here it is. http://play.golang.org/p/ASSGF5Oe9R
// Package main provides ...
package main
import "fmt"
type RootTrie []Trie
type Trie struct {
subtrie []Trie
index byte
}
func (trie *Trie) Insert(data string) *Trie {
if data != "" {
if trie.index == 0 {
trie.index = data[0]
}
if next := trie.containsIndex(data[1:]); next != nil {
//Problem Point
fmt.Println(string(data[1]), "found follwing", string(data[0]))
next.Insert(data[1:])
} else {
nt := &Trie{}
trie.subtrie = append(trie.subtrie, *nt.Insert(data[1:]))
}
}
return trie
}
func (trie *Trie) containsIndex(next string) *Trie {
if next != "" {
for _, st := range trie.subtrie {
if st.index == next[0] {
return &st
}
}
}
return nil
}
func main() {
t := &Trie{}
t = t.Insert("hanyang")
fmt.Println("result:", t)
t = t.Insert("hanyKk")
fmt.Println("result:", t)
t.Insert("hanyK")
}
The following problems happen in second "Insert",
the where I put, //Problem Point
I made containsIndex method for searching next linked trie, and it searched well actually.
But when I updated next property which containsIndex given, its not affected its mother struct trie though.
What I don't understand is I gave it reference type when returning containsIndex, but its still
act liked 'value copied', Why does it not affected its mother structure(trie)?
Thanks!
The problem is in method containsIndex. Golang range by default creates copy each element in slice and assigns copy of this value to st (in your example). Usually to preserve reference to element in slice you should use original slice and its index. In you case method containsIndex should look something like this:
func (trie *Trie) containsIndex(next string) *Trie {
if next != "" {
for i, st := range trie.subtrie {
if st.index == next[0] {
return &trie.subtrie[i]
}
}
}
return nil
}

in golang, general function to load http form data into a struct

In Go, http form data (e.g. from a POST or PUT request) can be accessed as a map of the form map[string][]string. I'm having a hard time converting this to structs in a generalizable way.
For example, I want to load a map like:
m := map[string][]string {
"Age": []string{"20"},
"Name": []string{"John Smith"},
}
Into a model like:
type Person struct {
Age int
Name string
}
So I'm trying to write a function with the signature LoadModel(obj interface{}, m map[string][]string) []error that will load the form data into an interface{} that I can type cast back to a Person. Using reflection so that I can use it on any struct type with any fields, not just a Person, and so that I can convert the string from the http data to an int, boolean, etc as necessary.
Using the answer to this question in golang, using reflect, how do you set the value of a struct field? I can set the value of a person using reflect, e.g.:
p := Person{25, "John"}
reflect.ValueOf(&p).Elem().Field(1).SetString("Dave")
But then I'd have to copy the load function for every type of struct I have. When I try it for an interface{} it doesn't work.
pi := (interface{})(p)
reflect.ValueOf(&pi).Elem().Field(1).SetString("Dave")
// panic: reflect: call of reflect.Value.Field on interface Value
How can I do this in the general case? Or even better, is there a more idiomatic Go way to accomplish what I'm trying to do?
You need to make switches for the general case, and load the different field types accordingly. This is basic part.
It gets harder when you have slices in the struct (then you have to load them up to the number of elements in the form field), or you have nested structs.
I have written a package that does this. Please see:
http://www.gorillatoolkit.org/pkg/schema
For fun, I tried it out. Note that I cheated a little bit (see comments), but you should get the picture. There is usually a cost to use reflection vs statically typed assignments (like nemo's answer), so be sure to weigh that in your decision (I haven't benchmarked it though).
Also, obvious disclaimer, I haven't tested all edge cases, etc, etc. Don't just copy paste this in production code :)
So here goes:
package main
import (
"fmt"
"reflect"
"strconv"
)
type Person struct {
Age int
Name string
Salary float64
}
// I cheated a little bit, made the map's value a string instead of a slice.
// Could've used just the index 0 instead, or fill an array of structs (obj).
// Either way, this shows the reflection steps.
//
// Note: no error returned from this example, I just log to stdout. Could definitely
// return an array of errors, and should catch a panic since this is possible
// with the reflect package.
func LoadModel(obj interface{}, m map[string]string) {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panic! %v\n", e)
}
}()
val := reflect.ValueOf(obj)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
// Loop over map, try to match the key to a field
for k, v := range m {
if f := val.FieldByName(k); f.IsValid() {
// Is it assignable?
if f.CanSet() {
// Assign the map's value to this field, converting to the right data type.
switch f.Type().Kind() {
// Only a few kinds, just to show the basic idea...
case reflect.Int:
if i, e := strconv.ParseInt(v, 0, 0); e == nil {
f.SetInt(i)
} else {
fmt.Printf("Could not set int value of %s: %s\n", k, e)
}
case reflect.Float64:
if fl, e := strconv.ParseFloat(v, 0); e == nil {
f.SetFloat(fl)
} else {
fmt.Printf("Could not set float64 value of %s: %s\n", k, e)
}
case reflect.String:
f.SetString(v)
default:
fmt.Printf("Unsupported format %v for field %s\n", f.Type().Kind(), k)
}
} else {
fmt.Printf("Key '%s' cannot be set\n", k)
}
} else {
// Key does not map to a field in obj
fmt.Printf("Key '%s' does not have a corresponding field in obj %+v\n", k, obj)
}
}
}
func main() {
m := map[string]string{
"Age": "36",
"Name": "Johnny",
"Salary": "1400.33",
"Ignored": "True",
}
p := new(Person)
LoadModel(p, m)
fmt.Printf("After LoadModel: Person=%+v\n", p)
}
I'd propose to use a specific interface instead of interface{} in your LoadModel
which your type has to implement in order to be loaded.
For example:
type Loadable interface{
LoadValue(name string, value []string)
}
func LoadModel(loadable Loadable, data map[string][]string) {
for key, value := range data {
loadable.LoadValue(key, value)
}
}
And your Person implements Loadable by implementing LoadModel like this:
type Person struct {
Age int
Name string
}
func (p *Person) LoadValue(name string, value []string) {
switch name {
case "Age":
p.Age, err = strconv.Atoi(value[0])
// etc.
}
}
This is the way, the encoding/binary package or the encoding/json package work, for example.

Resources