Traversing a Golang Map - go

I initialized a variable named data like this:
var data interface{}
Then I unmarshalled raw json into.
err = json.Unmarshal(raw, &data)
I've run these two functions on it:
fmt.Println(reflect.TypeOf(data))
fmt.Println(data)
and those return this:
map[string]interface {}
map[tasks:[map[payload:map[key:36A6D454-FEEE-46EB-9D64-A85ABEABBCB7] code_name:image_resize]]]
and I need to access the "key". I have tried these approaches and a few more:
data["tasks"][0]["payload"]["key"]
data[0][0][0][0]
Those have all given me an error similar to this one:
./resize.go:44: invalid operation: data["tasks"] (index of type interface {})
Any advice on how to grab the "key" value out of this interface? Thanks in advance.

Since you already know your schema, the best way to do this is to unmarshal directly into structs you can use.
http://play.golang.org/p/aInZp8IZQA
package main
import (
"encoding/json"
"fmt"
)
type msg struct {
Tasks []task `json:"tasks"`
}
type task struct {
CodeName string `json:"code_name"`
Payload payload `json:"payload"`
}
type payload struct {
Key string `json:"key"`
}
func main() {
var data msg
raw := `{ "tasks": [ { "code_name": "image_resize", "payload": { "key": "36A6D454-FEEE-46EB-9D64-A85ABEABBCB7" } } ] }`
err := json.Unmarshal([]byte(raw), &data)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(data.Tasks[0].Payload.Key)
}
If you insist on doing things the hard way using your original code, you need to do type assertions. I highly recommend avoiding this route when possible. It is not fun. Every step needs to be checked to ensure it matches the data structure you expect.
http://play.golang.org/p/fI5sqKV19J
if m, ok := data.(map[string]interface{}); ok {
if a, ok := m["tasks"].([]interface{}); ok && len(a) > 0 {
if e, ok := a[0].(map[string]interface{}); ok {
if p, ok := e["payload"].(map[string]interface{}); ok {
if k, ok := p["key"].(string); ok {
fmt.Println("The key is:", k)
}
}
}
}
}
In response to Goodwine's question: You can read further about how to marshal and unmarshal by reading the encoding/json godoc. I suggest starting here:
http://golang.org/pkg/encoding/json/#Marshal
http://golang.org/pkg/encoding/json/#Unmarshal

Another solution is to use 3rd-party package like https://github.com/Jeffail/gabs
With gabs, The example above by #stephen-weinberg can be written as:
raw := `{ "tasks": [ { "code_name": "image_resize", "payload": { "key": "36A6D454-FEEE-46EB-9D64-A85ABEABBCB7" } } ] }`
j, _ := gabs.ParseJSON([]byte(raw))
fmt.Println("The key is", j.S("tasks").Index(0).S("payload", "key").Data().(string))
other popular json handling packages I've stumbled upon are: https://github.com/bitly/go-simplejson and https://github.com/antonholmquist/jason.

Related

Parsing JSON using struct

I'm trying to parse JSON using Go. Can anyone tell me why my code is not working as expected?
package main
import (
"encoding/json"
"fmt"
)
type Message struct {
Name string
Body string
Time int64
}
type Person struct {
M Message
}
func get_content() {
body := []byte(`{"person":{"Name":"Alice","Body":"Hello","Time":1294706395881547000}}`)
var data Person
err := json.Unmarshal(body, &data)
if err != nil {
panic(err.Error())
}
fmt.Printf("%v",data.M.Name)
}
func main() {
get_content()
}
I'm expecting it to print the Name.
Go playground Code
There are two problems in the code.
The first one is what #umar-hayat mentioned above -> you are unmarshalling into the data object and you should be aiming at the data.M field.
The second problem is that your JSON's structure doesn't match your struct's structure. Your Person has a single field called M. If we want to represent this as JSON it would look like this:
{
"M": {
"Name": "Joe",
"Body": "Hi",
"time": 2600
}
}
Instead, you have a field called person in your JSON which cannot be matched to any field in your struct. The fact that it's similar to the name of the struct's type doesn't help in any way, I'm afraid.
So, you can either change your JSON and your target:
body := []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
var data Person
err := json.Unmarshal(body, &data.M)
Or just your JSON:
body := []byte(`{"M":{"Name":"Alice","Body":"Hello","Time":1294706395881547000}}`)
var data Person
err := json.Unmarshal(body, &data)
But it's essential that the names of the fields in your JSON match the names of the fields in your struct. Or, as mentioned by Konstantinos, you can use tags in order to specify particular names with which your struct's fields will be represented in the JSON.
You might find this helpful: https://gobyexample.com/json
Here is how to Unmarshel JSON to the struct. you can check it on Go Playground here:
package main
import (
"encoding/json"
"fmt"
)
type Message struct {
Name string
Body string
Time int64
}
type Person struct {
M Message
}
func get_content() {
body := []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
var data Person
err := json.Unmarshal(body, &data.M)
if err != nil {
panic(err.Error())
}
fmt.Printf(data.M.Name)
}
func main() {
get_content()
}
Replace data with data.M in below line.
err := json.Unmarshal(body, &data)
As long as you intent to map Json keys on structs whose fields have different names you should add tags:
type Message struct {
Name string `json:"Name"`
Body string `json:"Body"`
Time int64 `json:"Time"`
}
type Person struct {
M Message `json:"person"`
}
You can find more information here
In addition this answer explains in an nutshell the purpose of tags in go.

Unmarshal unknown JSON fields without structs

I am trying to unmarshal a JSON object which has an optional array, I am doing this without an array and this is what I got so far:
import (
"encoding/json"
"fmt"
)
func main() {
jo := `
{
"given_name": "Akshay Raj",
"name": "Akshay",
"country": "New Zealand",
"family_name": "Gollahalli",
"emails": [
"name#example.com"
]
}
`
var raw map[string]interface{}
err := json.Unmarshal([]byte(jo), &raw)
if err != nil {
panic(err)
}
fmt.Println(raw["emails"][0])
}
The emails field might or might not come sometime. I know I can use struct and unmarshal it twice for with and without array. When I try to get the index 0 of raw["emails"][0] I get the following error
invalid operation: raw["emails"][0] (type interface {} does not support indexing)
Is there a way to get the index of the emails field?
Update 1
I can do something like this fmt.Println(raw["emails"].([]interface{})[0]) and it works. Is this the only way?
The easiest way is with a struct. There's no need to unmarshal twice.
type MyStruct struct {
// ... other fields
Emails []string `json:"emails"`
}
This will work, regardless of whether the JSON input contains the emails field. When it is missing, your resulting struct will just have an uninitialized Emails field.
You can use type assertions. The Go tutorial on type assertions is here.
A Go playground link applying type assertions to your problem is here. For ease of reading, that code is replicated below:
package main
import (
"encoding/json"
"fmt"
)
func main() {
jo := `
{
"given_name": "Akshay Raj",
"name": "Akshay",
"country": "New Zealand",
"family_name": "Gollahalli",
"emails": [
"name#example.com"
]
}
`
var raw map[string]interface{}
err := json.Unmarshal([]byte(jo), &raw)
if err != nil {
panic(err)
}
emails, ok := raw["emails"]
if !ok {
panic("do this when no 'emails' key")
}
emailsSlice, ok := emails.([]interface{})
if !ok {
panic("do this when 'emails' value is not a slice")
}
if len(emailsSlice) == 0 {
panic("do this when 'emails' slice is empty")
}
email, ok := (emailsSlice[0]).(string)
if !ok {
panic("do this when 'emails' slice contains non-string")
}
fmt.Println(email)
}
As always you can use additional libraries for work with your json data. For example with gojsonq package it will like so:
package main
import (
"fmt"
"github.com/thedevsaddam/gojsonq"
)
func main() {
json := `
{
"given_name": "Akshay Raj",
"name": "Akshay",
"country": "New Zealand",
"family_name": "Gollahalli",
"emails": [
"name#example.com"
]
}
`
first := gojsonq.New().JSONString(json).Find("emails.[0]")
if first != nil {
fmt.Println(first.(string))
} else {
fmt.Println("There isn't emails")
}
}

Should I be able to type assert a slice of string maps?

I am receiving a message using the Go NSQ library where a field is a slice of map[string]string's. I feel like I should be able to type assert this field as value.([]map[string]string) but it's failing and I can't tell if this is expected or not.
This snippet replicates the behavior https://play.golang.org/p/qcZM880Nal
Why does this type assertion fail?
This is covered briefly here in the FAQ.
The types []interface{} and []map[string]string have two different representation in memory. There is no direct way to convert between them.
Also, even when a conversion is allowed, you should note that you can't successfully assert to a different basic type at all (http://play.golang.org/p/zMp1qebIZZ). You can only assert to the original type, or another type of interface,
// panics
var i interface{} = int32(42)
_ = i.(int64)
The conversion referred to doesn't work as described in Jim's answer. However, if you actually have the type you claim, and the interface you state it implements is just interface{} then the type assertion works fine. I don't want to speculate on the details of why the other doesn't work but I believe it's because you would have to unbox it in two phases as the map[string]string's inside the slice are actually being viewed as some interface{} themselves. Here's an example;
package main
import "fmt"
func main() {
var value interface{}
value = []map[string]string{{"address": string("this is interface literal")}}
// value = []map[string]string{{"address": "this is map literal"}}
AssertIt(value)
}
func AssertIt(value interface{}) {
if str, ok := value.([]map[string]string); ok && len(str) > 0 {
fmt.Println(str[0]["address"])
} else {
fmt.Println("nope")
}
}
https://play.golang.org/p/hJfoh_havC
you can do it by reflect in 2022
res := `[
{
"name": "jhon",
"age": 35
},
{
"name": "wang",
"age": 30
}
]`
// res := `{
// "name": "jhon",
// "age": 35
// }`
var rst any
err := json.Unmarshal([]byte(res), &rst)
if err != nil {
panic(err)
}
t := reflect.TypeOf(rst).Kind()
fmt.Println(t)

Golang Json Not returning expected values

I have a code # http://play.golang.org/p/HDlJJ54YqW
I wanted to print the Phone and email of a person.
It can be of multiple entries.
But getting the error undefined.
Can anyone help out.
Thanks.
Small details: you are referencing twice: you give the address of the address of the object to json.Unmarshal. Just give the address.
` allows for multiline, no need to split your json input.
I don't know what you where trying to achieve with u.Details[Phone:"1111"].Email, but this is no Go syntax. your Details member is a slice off Detail. A slice is similar to an array and can be accessed by index.
Also, your json does not match your object structure. If you want to have multiple details in one content, then it needs to be embed in an array ([ ])
You could do something like this: (http://play.golang.org/p/OP1zbPW_wk)
package main
import (
"encoding/json"
"fmt"
)
type Content struct {
Owner string
Details []*Detail
}
type Detail struct {
Phone string
Email string
}
func (c *Content) SearchPhone(phone string) *Detail {
for _, elem := range c.Details {
if elem.Phone == phone {
return elem
}
}
return nil
}
func (c *Content) SearchEmail(email string) *Detail {
for _, elem := range c.Details {
if elem.Email == email {
return elem
}
}
return nil
}
func main() {
encoded := `{
"Owner": "abc",
"Details": [
{
"Phone": "1111",
"Email": "#gmail"
},
{
"Phone": "2222",
"Email": "#yahoo"
}
]
}`
// Decode the json object
u := &Content{}
if err := json.Unmarshal([]byte(encoded), u); err != nil {
panic(err)
}
// Print out Email and Phone
fmt.Printf("Email: %s\n", u.SearchPhone("1111").Email)
fmt.Printf("Phone: %s\n", u.SearchEmail("#yahoo").Phone)
}

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