Nested maps in Golang - dictionary

func main() {
var data = map[string]string{}
data["a"] = "x"
data["b"] = "x"
data["c"] = "x"
fmt.Println(data)
}
It runs.
func main() {
var data = map[string][]string{}
data["a"] = append(data["a"], "x")
data["b"] = append(data["b"], "x")
data["c"] = append(data["c"], "x")
fmt.Println(data)
}
It also runs.
func main() {
var w = map[string]string{}
var data = map[string]map[string]string{}
w["w"] = "x"
data["a"] = w
data["b"] = w
data["c"] = w
fmt.Println(data)
}
It runs again!
func main() {
var data = map[string]map[string]string{}
data["a"]["w"] = "x"
data["b"]["w"] = "x"
data["c"]["w"] = "x"
fmt.Println(data)
}
But it fails!?
Is there a problem with nested maps in Go? Or is there no multiple bracket support for nested maps?

The zero value for map types is nil. It is not yet initialized. You cannot store values in a nil map, that's a runtime panic.
In your last example you initialize the (outer) data map, but it has no entries. When you index it like data["a"], since there is no entry with "a" key in it yet, indexing it returns the zero value of the value type which is nil for maps. So attempting to assign to data["a"]["w"] is a runtime panic.
You have to initialize a map first before storing elements in it, for example:
var data = map[string]map[string]string{}
data["a"] = map[string]string{}
data["b"] = make(map[string]string)
data["c"] = make(map[string]string)
data["a"]["w"] = "x"
data["b"]["w"] = "x"
data["c"]["w"] = "x"
fmt.Println(data)
Output (try it on the Go Playground):
map[a:map[w:x] b:map[w:x] c:map[w:x]]
Note that when you declare a variable of map type and initialize it with a composite literal (as in var data = map[string]string{}), that also counts as initializing.
Note that you may also initialize your nested maps with a composite literal:
var data = map[string]map[string]string{
"a": map[string]string{},
"b": map[string]string{},
"c": map[string]string{},
}
data["a"]["w"] = "x"
data["b"]["w"] = "x"
data["c"]["w"] = "x"
fmt.Println(data)
Output is the same. Try it on the Go Playground.

While the most straightforward answer to this question is to initialize your nested maps as previously described, there is another potential option depending on your access pattern. If you need a truly hierarchical system of maps, then the previous answers are just fine. However, if you simply need to look up values in the map using multiple facets, read on!
It is totally acceptable for maps to use structs as keys (in fact, anything that is comparable can be used). Thus, you can use a single map with struct keys like this example from the Golang blog, which is a hit counter that tracks page hits by country:
type Key struct {
Path, Country string
}
hits := make(map[Key]int)
// set: Vietnamese person visiting the home page
hits[Key{"/", "vn"}]++
// get: see how many Chinese persons read the spec
n := hits[Key{"/ref/spec", "cn"}]
I don't see maps like this often enough, instead many people reach for the nested variant first, which I think may not always be the right fit.

In addition to icza's answer. Map initialization can be written in short form:
var data = map[string]map[string]string{
"a": map[string]string{
"w": "x"},
"b": map[string]string{
"w": "x"},
"c": map[string]string{
"w": "x"},
"d": map[string]string{},
}
fmt.Println(data)
Output is the same. Try it on the Go Playground. The key "d" added to demonstrate the mapping with an empty map.

The below solution might be useful for you.
var data = map[string]interface{}{
"publishInfo": map[string]interface{}{
"title": publishInfo.Title,
"description": publishInfo.Desc,
"thumbnail": publishInfo.ImageSrc,
"url": publishInfo.URL,
"tags": publishInfo.Tags,
},
"revision": draftInfo.Revision,
}

Related

Creating a flat array from the map entries in Go

What is the shortest (and idiomatic) way to create an array from the keys and values of a map w/o compromising on time complexity too much?
For instance, from the following map:
map[string]string { "1":"a", "2":"b" }
I need to create the following array:
[]string{"1","a", "2","b"}
I can do this in Scala with following:
val myMap = Map("1" -> "a", "2" -> "b")
myMap.keySet ++ myMap.values
Thank you.
Simplest way would be to just iterate the map, since in Go the syntax would allow direct access to keys and values and dump them into the array.
m := map[string]string { "1":"a", "2":"b" }
arr := []string{}
for k, v := range m {
arr = append(arr, k, v)
}
One caveat here: In Go, map iteration order is randomized, as you can see here, under "Iteration Order":
https://blog.golang.org/go-maps-in-action
So if you want your resulting array to have a particular ordering, you should first dump the keys and order (as shown in that same blog entry).
Playground (without the sorting part):
https://play.golang.org/p/mCe6eEy25A

Assigning a value to struct member through reflection in Go

I have a struct v with members A, B, C string. Using reflection, I can get the names of the fields and their values:
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
fieldname := fi.Name
fmt.Println(fieldname)
val := fmt.Sprintf("%v", v.Field(i).Interface())
}
since I have the name, and can get the value OUT, can I assign new values to the fields? I would like to do essentially:
v.Field(fieldname).Interface() = "new value"
but that obviously doesn't work. Is it possible to assign a value into a struct if you only know the name of the field?
In practice, I'm trying to assign values from a map[string]string to corresponding fields in a struct, where the struct and map definitions may expand of change over time, and the map may contain more, or less, values than the struct. I've considered doing it w/JSON, but that approach leaves me a little cold, seeing as how easy it was to use reflection to get "almost" there!
Thanks,
Ken
Yes, it is possible.
Introduction
Since you want to access and modify the value of a variable (or field), you need to use the reflect.Value type instead of reflect.Type. You can acquire it with reflect.ValueOf(). Also in order to modify it with reflection, you need to pass the address (a pointer) of the struct or value you want to modify (else you could only read it but not modify it).
But you don't want to modify the address/pointer but the pointed value, so you have to "navigate" from the Value of the pointer to the Value of the pointed variable (struct), this is what Value.Elem() is for. It looks like this: reflect.ValueOf(&s).Elem()
You can get the Value of a struct field with the Value.FieldByName() method, which since we passed the address of the pointer to the ValueOf() function will be settable.
The Code
The code is much simpler than the introduction once you understand it. You can also try it on the Go Playground:
var s struct {
A, B, C string
}
s.A, s.B, s.C = "a1", "b2", "c3"
fmt.Println("Before: ", s)
v := reflect.ValueOf(&s).Elem()
v.FieldByName("A").SetString("2a")
v.FieldByName("B").SetString("2b")
v.FieldByName("C").SetString("2c")
fmt.Println("After: ", s)
// Using a map:
m := map[string]string{"A": "ma", "B": "mb", "C": "mc"}
for mk, mv := range m {
v.FieldByName(mk).SetString(mv)
}
fmt.Println("From Map:", s)
Output:
Before: {a1 b2 c3}
After: {2a 2b 2c}
From Map: {ma mb mc}
I recommend to read this blog post to learn the basics of the reflection in Go:
The Laws of Reflection

How can I merge two maps in go?

I have a recursive function that creates objects representing file paths (the keys are paths and the values are info about the file). It's recursive as it's only meant to handle files, so if a directory is encountered, the function is recursively called on the directory.
All that being said, I'd like to do the equivalent of a set union on two maps (i.e. the "main" map updated with the values from the recursive call). Is there an idiomatic way to do this aside from iterating over one map and assigning each key, value in it to the same thing in the other map?
That is: given a,b are of type map [string] *SomeObject, and a and b are eventually populated, is there any way to update a with all the values in b?
There is no built in way, nor any method in the standard packages to do such a merge.
The idomatic way is to simply iterate:
for k, v := range b {
a[k] = v
}
Since Go 1.18, you can simply use the Copy function from the golang.org/x/exp/maps package:
package main
import (
"fmt"
"golang.org/x/exp/maps"
)
func main() {
src := map[string]int{
"one": 1,
"two": 2,
}
dst := map[string]int{
"two": 2,
"three": 3,
}
maps.Copy(dst, src)
fmt.Println("src:", src)
fmt.Println("dst:", dst)
}
(Playground)
Output:
src: map[one:1 two:2]
dst: map[one:1 three:3 two:2]
One caveat of this approach is that, in Go versions 1.18.x to 1.19.x, your map's key type must be concrete, i.e. not an interface type. For instance, the compiler won't allow you to pass values of type map[io.Reader]int to the Copy function:
package main
import (
"fmt"
"io"
"golang.org/x/exp/maps"
)
func main() {
var src, dst map[io.Reader]int
maps.Copy(dst, src)
fmt.Println("src:", src)
fmt.Println("dst:", dst)
}
(Playground)
Compiler output:
go: finding module for package golang.org/x/exp/maps
go: downloading golang.org/x/exp v0.0.0-20220328175248-053ad81199eb
./prog.go:12:11: io.Reader does not implement comparable
Go build failed.
This limitation was lifted in Go 1.20.
(Playground)
Starting at go 1.18, thanks to the release of the Generics feature, there are now generic functions that union maps!
You can use a package like https://github.com/samber/lo in order to do so.
Note that the key can be of any "comparable" type, while the value can be of any type.
Example:
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
map1 := map[string]interface{}{"k1": "v1", "k2": 2}
map2 := map[string]interface{}{"k2": "v2new", "k3": true}
map1 = lo.Assign(map1, map2)
fmt.Printf("%v", map1)
}
The result is:
map[k1:v1 k2:v2new k3:true]
If you have a couple of nested maps, left and right, then this function will recursively add the items from right into left. If the key is already in left then we recurse deeper into the structure and attempt only add keys to left (e.g. never replace them).
type m = map[string]interface{}
// Given two maps, recursively merge right into left, NEVER replacing any key that already exists in left
func mergeKeys(left, right m) m {
for key, rightVal := range right {
if leftVal, present := left[key]; present {
//then we don't want to replace it - recurse
left[key] = mergeKeys(leftVal.(m), rightVal.(m))
} else {
// key not in left so we can just shove it in
left[key] = rightVal
}
}
return left
}
NOTE: I do not handle the case in which the value is not itself a map[string]interface{}. So if you have left["x"] = 1 and right["x"] = 2 then the above code will panic when attempting leftVal.(m).
Here is another option,
in case you are trying to limit the number of third-party dependencies such github.com/samber/lo, OR
you are not comfortable with the experimental nature of golang.org/x/exp (read the warning), OR
you would rather the convenience of an append()-like API instead of exp.Copy() from golang.org/x/exp (append accepts any number of lists, whereas Copy() accepts only 2).
However it requires Go 1.18+ as it uses go generics.
Save the following in one of your modules/packages:
func MergeMaps[M ~map[K]V, K comparable, V any](src ...M) M {
merged := make(M)
for _, m := range src {
for k, v := range m {
merged[k] = v
}
}
return merged
}
Then you can use it very similarly to append():
func main() {
mergedMaps := MergeMaps(
map[string]int{"a": 1, "b": 2},
map[string]int{"b": 3, "c": 4},
map[string]int{"c": 3, "d": 4},
)
fmt.Println(mergedMaps)
}
Go is limited by what type of map it is. I'd suspect that there isn't built in functions because of the infinite number of type declarations that could exist for a map. So you have to build your own Merge functions depending on what type of map you are using:
func MergeJSONMaps(maps ...map[string]interface{}) (result map[string]interface{}) {
result = make(map[string]interface{})
for _, m := range maps {
for k, v := range m {
result[k] = v
}
}
return result
}

maps - deleting data

How does one delete data from a map in Go? For example, having
m := map[string]string{ "key1":"val1", "key2":"val2" };
I want to make m drop the "key1" without copying the entire map by iterating over its keys (which could get big in some uses). Is it enough to assign a nil value to "key1", or will that still keep the key in the map structure with an assigned value of nil? That is, if I later iterate over the keys of the map, will "key1" appear?
Deletion of map elements
The built-in function delete removes the element with key k from a map
m.
delete(m, k) // remove element m[k] from map m
For example,
package main
import "fmt"
func main() {
m := map[string]string{"key1": "val1", "key2": "val2"}
fmt.Println(m)
delete(m, "key1")
fmt.Println(m)
}
Output:
map[key1:val1 key2:val2]
map[key2:val2]

How to check if a map contains a key in Go?

I know I can iterate over a map m with
for k, v := range m { ... }
and look for a key, but is there a more efficient way of testing for a key's existence in a map?
Here's how you check if a map contains a key.
val, ok := myMap["foo"]
// If the key exists
if ok {
// Do something
}
This initializes two variables. val is the value of "foo" from the map if it exists, or a "zero value" if it doesn't (in this case the empty string). ok is a bool that will be set to true if the key existed.
If you want, you can shorten this to a one-liner.
if val, ok := myMap["foo"]; ok {
//do something here
}
Go allows you to put an initializing statement before the condition (notice the semicolon) in the if statement. The consequence of this is that the scope ofval and ok will be limited to the body of the if statement, which is helpful if you only need to access them there.
In addition to The Go Programming Language Specification, you should read Effective Go. In the section on maps, they say, amongst other things:
An attempt to fetch a map value with a key that is not present in the
map will return the zero value for the type of the entries in the map.
For instance, if the map contains integers, looking up a non-existent
key will return 0. A set can be implemented as a map with value type
bool. Set the map entry to true to put the value in the set, and then
test it by simple indexing.
attended := map[string]bool{
"Ann": true,
"Joe": true,
...
}
if attended[person] { // will be false if person is not in the map
fmt.Println(person, "was at the meeting")
}
Sometimes you need to distinguish a missing entry from a zero value.
Is there an entry for "UTC" or is that 0 because it's not in the map
at all? You can discriminate with a form of multiple assignment.
var seconds int
var ok bool
seconds, ok = timeZone[tz]
For obvious reasons this is called the “comma ok” idiom. In this
example, if tz is present, seconds will be set appropriately and ok
will be true; if not, seconds will be set to zero and ok will be
false. Here's a function that puts it together with a nice error
report:
func offset(tz string) int {
if seconds, ok := timeZone[tz]; ok {
return seconds
}
log.Println("unknown time zone:", tz)
return 0
}
To test for presence in the map without worrying about the actual
value, you can use the blank identifier (_) in place of the usual
variable for the value.
_, present := timeZone[tz]
Searched on the go-nuts email list and found a solution posted by Peter Froehlich on 11/15/2009.
package main
import "fmt"
func main() {
dict := map[string]int {"foo" : 1, "bar" : 2}
value, ok := dict["baz"]
if ok {
fmt.Println("value: ", value)
} else {
fmt.Println("key not found")
}
}
Or, more compactly,
if value, ok := dict["baz"]; ok {
fmt.Println("value: ", value)
} else {
fmt.Println("key not found")
}
Note, using this form of the if statement, the value and ok variables are only visible inside the if conditions.
Short Answer
_, exists := timeZone[tz] // Just checks for key existence
val, exists := timeZone[tz] // Checks for key existence and retrieves the value
Example
Here's an example at the Go Playground.
Longer Answer
Per the Maps section of Effective Go:
An attempt to fetch a map value with a key that is not present in the map will return the zero value for the type of the entries in the map. For instance, if the map contains integers, looking up a non-existent key will return 0.
Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for "UTC" or is that the empty string because it's not in the map at all? You can discriminate with a form of multiple assignment.
var seconds int
var ok bool
seconds, ok = timeZone[tz]
For obvious reasons this is called the “comma ok” idiom. In this example, if tz is present, seconds will be set appropriately and ok will be true; if not, seconds will be set to zero and ok will be false. Here's a function that puts it together with a nice error report:
func offset(tz string) int {
if seconds, ok := timeZone[tz]; ok {
return seconds
}
log.Println("unknown time zone:", tz)
return 0
}
To test for presence in the map without worrying about the actual value, you can use the blank identifier (_) in place of the usual variable for the value.
_, present := timeZone[tz]
Have a look at this snippet of code
nameMap := make(map[string]int)
nameMap["river"] = 33
v ,exist := nameMap["river"]
if exist {
fmt.Println("exist ",v)
}
As noted by other answers, the general solution is to use an index expression in an assignment of the special form:
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
var v, ok T = a[x]
This is nice and clean. It has some restrictions though: it must be an assignment of special form. Right-hand side expression must be the map index expression only, and the left-hand expression list must contain exactly 2 operands, first to which the value type is assignable, and a second to which a bool value is assignable. The first value of the result of this special form will be the value associated with the key, and the second value will tell if there is actually an entry in the map with the given key (if the key exists in the map). The left-hand side expression list may also contain the blank identifier if one of the results is not needed.
It's important to know that if the indexed map value is nil or does not contain the key, the index expression evaluates to the zero value of the value type of the map. So for example:
m := map[int]string{}
s := m[1] // s will be the empty string ""
var m2 map[int]float64 // m2 is nil!
f := m2[2] // f will be 0.0
fmt.Printf("%q %f", s, f) // Prints: "" 0.000000
Try it on the Go Playground.
So if we know that we don't use the zero value in our map, we can take advantage of this.
For example if the value type is string, and we know we never store entries in the map where the value is the empty string (zero value for the string type), we can also test if the key is in the map by comparing the non-special form of the (result of the) index expression to the zero value:
m := map[int]string{
0: "zero",
1: "one",
}
fmt.Printf("Key 0 exists: %t\nKey 1 exists: %t\nKey 2 exists: %t",
m[0] != "", m[1] != "", m[2] != "")
Output (try it on the Go Playground):
Key 0 exists: true
Key 1 exists: true
Key 2 exists: false
In practice there are many cases where we don't store the zero-value value in the map, so this can be used quite often. For example interfaces and function types have a zero value nil, which we often don't store in maps. So testing if a key is in the map can be achieved by comparing it to nil.
Using this "technique" has another advantage too: you can check existence of multiple keys in a compact way (you can't do that with the special "comma ok" form). More about this: Check if key exists in multiple maps in one condition
Getting the zero value of the value type when indexing with a non-existing key also allows us to use maps with bool values conveniently as sets. For example:
set := map[string]bool{
"one": true,
"two": true,
}
fmt.Println("Contains 'one':", set["one"])
if set["two"] {
fmt.Println("'two' is in the set")
}
if !set["three"] {
fmt.Println("'three' is not in the set")
}
It outputs (try it on the Go Playground):
Contains 'one': true
'two' is in the set
'three' is not in the set
See related: How can I create an array that contains unique strings?
var d map[string]string
value, ok := d["key"]
if ok {
fmt.Println("Key Present ", value)
} else {
fmt.Println(" Key Not Present ")
}
var empty struct{}
var ok bool
var m map[string]struct{}
m = make(map[string]struct{})
m["somestring"] = empty
_, ok = m["somestring"]
fmt.Println("somestring exists?", ok)
_, ok = m["not"]
fmt.Println("not exists?", ok)
Then, go run maps.go
somestring exists? true
not exists? false
It is mentioned under "Index expressions".
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.
A two value assignment can be used for this purpose. Please check my sample program below
package main
import (
"fmt"
)
func main() {
//creating a map with 3 key-value pairs
sampleMap := map[string]int{"key1": 100, "key2": 500, "key3": 999}
//A two value assignment can be used to check existence of a key.
value, isKeyPresent := sampleMap["key2"]
//isKeyPresent will be true if key present in sampleMap
if isKeyPresent {
//key exist
fmt.Println("key present, value = ", value)
} else {
//key does not exist
fmt.Println("key does not exist")
}
}
Example usage: Looping through a slice, for pairMap checking if key exists.
It an algorithm to find all pairs that adds to a specific sum.
func findPairs(slice1 []int, sum int) {
pairMap := make(map[int]int)
for i, v := range slice1 {
if valuei, ok := pairMap[v]; ok {
fmt.Println("Pair Found", i, valuei)
} else {
pairMap[sum-v] = i
}
}
}

Resources