2

I have a map[string]string and I need to test if some keys exist and if they do, convert some of the values to integers. For example:

m := map[string]string{"a": "b", "c": "d", "e": "f"} if v1, ok := m["a"]; ok { if v2, ok := m["c"]; ok { if i1, err := strconv.Atoi(v1); err != nil { if i2, err := strconv.Atoi(v2); err != nil { // do something with i1, i2 } } } } 

I find myself thinking in the lines of:

if m.exists("a") && m.exists("c") && is_int(m["a"]) && is_int(m["c"]) { // do something with atoi(m["a"]) and atoi(m["c"]) } 

... but that's not convenient with Go's standard library. So, what is the Go way of doing it?

2 Answers 2

3

Testing if a string value is a number and converting it to a number is basically the same amount of work.

So I would rather use the following util function which besides checking the keys and testing the values also returns the converted numbers:

func getInts(m map[string]string, keys ...string) (is []int, err error) { for _, k := range keys { v, ok := m[k] if !ok { return is, fmt.Errorf("%s is missing", k) } var i int if i, err = strconv.Atoi(v); err != nil { return } is = append(is, i) } return } 

Testing it with 3 different cases:

maps := []map[string]string{ {}, {"a": "b", "c": "d", "e": "f"}, {"a": "1", "c": "2", "e": "f"}, } for _, m := range maps { if is, err := getInts(m, "a", "c"); err == nil { fmt.Println("Numbers:", is) } else { fmt.Println("Error:", err) } } 

Output (try it on the Go Playground):

Error: a is missing Error: strconv.Atoi: parsing "b": invalid syntax Numbers: [1 2] 
Sign up to request clarification or add additional context in comments.

Comments

1

One way to do it is to create your own map[string]string type and add some simple methods to that. Here is a simple example:

type mapExists map[string]string func (m mapExists) exists(key string) bool { _, ok := m[key] return ok } func (m mapExists) isInt(key string) bool { v, ok := m[key] if !ok { return false } _, err := strconv.ParseInt(v, 10, 64) return err == nil } 

You can convert a map[string]string to the mapExists with:

m := map[string]string{ "a": "b", "c": "2", } m2 := mapExists(m) 

And then call stuff like in your example:

fmt.Println("a exists:\t", m2.exists("a")) fmt.Println("asd exists:\t", m2.exists("asd")) fmt.Println("a isInt:\t", m2.isInt("a")) fmt.Println("c isInt:\t", m2.isInt("c")) 

Which will output:

a exists: true asd exists: false a isInt: false c isInt: true 

Whether this is the "best" or "idiomatic" way is a matter of opinion and depends on the circumstances. In general, I would (personally) prefer to not use these wrappers. Your above example could be rewritten to something like the following :

func test() { m := map[string]string{"a": "b", "c": "d", "e": "f"} v1, v1ok := m["a"] v2, v2ok := m["c"] if !v1ok || !v2ok { return } i1, err := strconv.Atoi(v1) if err != nil { return } i2, err := strconv.Atoi(v2) { if err != nil { return } } 

Which I think is much more readable. It's more verbose, but Go is a verbose language ;-)

But again, it depends on the entirety of your codebase. If you're calling this pattern very often then a custom type might be a good solution (at the cost of slightly lower performance, which may or may not be a problem).

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.