How to retrieve nested map data "json.unmarshal()" as empty interface - json

This seems to be an easy question, but I haven't figured out how to do it: I have a nested map item, which when printed out is like the following:
fmt.Println("s3Info:", s3Info)
printout:
s3Info: [[map[s3Config:map[bucket:testbucket-data s3Key:runs/6033fd684304200011ef3bc5/init/03a78d21-446a-41bc-b4c1-eb66e04f45e2/52c8a076-f6c4-4180-8625-38ca52482628] size:158971 type:s3 varType:File]]
I wonder how can I get the value of bucket and s3Key from the object s3Info?
I tried to use s3Info.s3Config to access s3Config, but then got the following error:
go/api_default_service_data_item.go:659:46: s3Info.s3Config undefined (type interface {} is interface with no methods)
I also tried to use s3Info["s3Config"] to access s3Config, but then got the following error:
go/api_default_service_data_item.go:660:46: invalid operation: s3Info["s3Config"] (type interface {} does not support indexing)
ADDED:
The code is part of a program which processes the query response from an API endpoint, the following is the code:
var runData map[string]interface{}
json.Unmarshal(body, &runData)
p := runData["p"].(map[string]interface{})
init := p["init"].(map[string]interface{})
outputs := init["outputs"].(map[string]interface{})
for key, s3Info := range outputs {
// printout s3Info
fmt.Println("s3Info:", s3Info)
// check type
switch c := s3Info.(type) {
case string:
fmt.Println("Key:", key, "=>", "s3Info:", s3Info)
default:
fmt.Printf("s3Info Type: %T\n", c)
}
// type assert to map
s3Info := outputs[key].(map[string]interface{})
fmt.Println("Key:", key, "=>", "s3Config:", s3Info["s3Config"])
}
The printout is as follows:
s3Info: [map[s3Config:map[bucket:testbucket-data s3Key:runs/6033fd684304200011ef3bc5/init/03a78d21-446a-41bc-b4c1-eb66e04f45e2/52c8a076-f6c4-4180-8625-38ca52482628] size:158971 type:s3 varType:File]]
s3Info Type: []interface {}
interface conversion: interface {} is []interface {}, not map[string]interface {}

The s3Info was unmarshalled by json.Unmarshal() into an array of interface{} but not a map. The contents inside can be retrieved through type assertion to []interface{}.
s3Config can be obtained through:
for _, s := range s3Info.([]interface{}) {
s3Config := s.(map[string]interface{})["s3Config"]
}
Thanks #brits for the useful link:

Related

Go: is it possible to marshal a cycled json

I'm coding Go to create a Circular Singly Linked List: https://www.geeksforgeeks.org/circular-singly-linked-list-insertion/
Here is my code:
type Node struct {
Name string `json:"name"`
Next *Node `json:"next"`
}
func main() {
n1 := Node{Name: "111", Next: nil}
n2 := Node{Name: "222", Next: &n1}
n3 := Node{Name: "333", Next: &n2}
n1.Next = &n3
}
However, when I try to marshal, I get an error: json: unsupported value: encountered a cycle via *main.Node
res, err := json.Marshal(n1)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(res)
}
Well, I understand why I get this error. So I want to know if there is some method to allow me to convert the struct of Node to JSON.
From the documentation of encoding/json.Marshal
JSON cannot represent cyclic data structures and Marshal does not handle them. Passing cyclic structures to Marshal will result in an error.
The main part is "JSON cannot represent cyclic data structures" so the answer is a clear No: It is not possible to have JSON which represents your value. You must redesign.

Parsing data from AWS api using Golang

I am using the Connect API to get all the contact flows from a particular instance and want to store them in a DynamoDB.
type contactFlow struct {
Arn string
ContactFlowType string
Id string
Name string
}
func HandleRequest(ctx context.Context) (string, error) {
var contactFlowDetails []contactFlow
mySession := session.Must(session.NewSession())
connectSession := connect.New(mySession)
connectInstance := &connect.ListContactFlowsInput{
InstanceId: aws.String("INSTANCE_ID"),
}
connectResult, connectError := connectSession.ListContactFlows(connectInstance)
connectResultFlow := connectResult.ContactFlowSummaryList
connectFlowSummaryList := awsutil.Prettify(connectResultFlow)
fmt.Println(connectFlowSummaryList)
json.Unmarshal([]byte(connectFlowSummaryList), &contactFlowDetails)
fmt.Println(contactFlowDetails)
The API that I am trying to use is this: https://docs.aws.amazon.com/sdk-for-go/api/service/connect/#ListContactFlowsOutput
I do get the result when I print out connectFlowSummaryList on CloudWatch Logs but it always returns an empty array [] when I print out contactFlowDetails.
Edit 1: I think I found what could be the potential problem while doing this decoding. The result from the logs look something like this:
[
{
Arn: "INSTANCE_ID",
ContactFlowType: "AGENT_WHISPER",
Id: "CONTACT_FLOW_ID",
Name: "Default agent whisper"
}
]
The key values of the result are not present inside double-inverted commas, how could I go about decoding such a result?
Thanks!
What you should do is marshal connectResultFlow.ContactFlowSummaryList to a json string before passing it to awsutil.Prettify (if you need to).
You can also, skip awsutil.Prettify completely, to arrive at this:
connectResultFlow := connectResult.ContactFlowSummaryList
b, err := json.Marshal(connectResultFlow)
if err != nil {
return "", err
}
json.Unmarshal(b, &contactFlowDetails)
fmt.Println(contactFlowDetails)

Iterating over json using reflection

I'm trying to consume a rest endpoint in my golang project. The JSON structure is pretty large and is semi-structured, so I'm using reflection to iterate over it and grab the information that I am interested in.
Here is how I'm unmarshal-ing the response:
var m map[string]interface{}
json.Unmarshal(bytes, &m)
But the part I'm stuck at is - I'm iterating through a slice of maps (i think) but I'm unable to get the keys or values of the map. Here is the code in question.
if val, ok := m["messages"]; ok {
s := reflect.ValueOf(val)
if s.Kind() == reflect.Slice {
for i := 0; i < s.Len(); i++ {
item := s.Index(i)
fmt.Println("VALUE = ", item.Elem())
fmt.Println("KIND = ", item.Kind())
}
}
return
}
When I run the code the value that is displayed looks like a map:
map[decoration_stats:<nil> highlight_ranges:map[] index:graylog_7 message:map[_id:49272694-1834-11ea-8928-0242ac120004 docker:{"container_id":"0f9d97722c25240c6f99487b247b2416177a749de47d661cd661334514e0e74f"} facility:fluentd gl2_message_id:01DVDSM9VSDQ5PF81T4C31NSH6....
And the kind is:
KIND = interface
I tried various things like:
for _, e := range val.MapKeys() {
v := val.MapIndex(e)
fmt.Println(v)
}
But the code panics with:
panic: reflect: call of reflect.Value.MapKeys on interface Value
Sorry, I'm somewhat new to golang but have used other static typed language, mainly Java, when it comes to any reflection type programming.
My question is how to can I convert this interface to a map or some concrete type so that I can use it. Any help would be greatly appreciated.
Using reflection is an inefficient way to do this. JSON unmarshal, when used with an interface (and map[string]interface{}) produces a limited set of types, and you can use type assertions or a type-switch to deal with it:
if val, ok := m["messages"]; ok {
switch v:=val.(type) {
case map[string]interface{}: // JSON object
for key, value:=range v {
}
case []interface{}: // JSON array
for i,node:=range v {
}
case string: // string value
case float64: // numeric value
case bool: // boolean value
case json.Number: // If you use json.Decoder with UseNumber()
}
}

golang fails to parse json for reflection created object

I try to write simple message protocol in go and i've encountered a problem. I have a lot of message types and i want to have a dictionary like this to manipulate with messages:
var dict map[reflect.Type]int = map[reflect.Type]int{
reflect.TypeOf(DataMessage{}): 1000,
reflect.TypeOf(TextMessage{}): 1001,
//....
}
func GetMessageTypeId(value interface{}) int {
if id, ok := dict[reflect.TypeOf(value)]; ok {
return id
} else {
return -1
}
}
func GetValueByTypeId(typeId int) interface{} {
for typeDec, id := range dict {
if id == typeId {
return reflect.Zero(typeDec).Interface()
}
}
fmt.Println("Unknown message type", typeId)
return nil
}
It works fine, but when i instantiate message with GetValueByTypeId and try to unmarshall json into it - i receive map[string]interface instead of my message.
I've made simple example to reproduce the problem:
http://play.golang.org/p/QEyDN9vztr
Please read this article: http://research.swtch.com/interfaces, especially the "Memory Optimizations".
The interface{} by definition consists of two pointers - to method table (e.g. type) and to data it holds. So for
var destination3 interface{} = reflect.Zero(reflect.TypeOf(Message{})).Interface()
it is empty method table (as interface{} has no methods) and reference to Message{}. Taking reference from it returns the reference to this struct so the unmarhal overwrites it with whatever matches interface{}.
If the data interface{} variable holds is a pointer itself, then it is optimized in a way that this pointer is used instead creating interface{} structure. So getting reference to it gives the reference to original variable.
http://play.golang.org/p/KsIS29rUAX
package main
import "fmt"
func main() {
var m1 struct{ Data string }
var m2 interface{}
var m3 interface{}
m2 = &m1
m3 = m1
fmt.Printf("&m1=%p m2=%p &m3=%p\n", &m1, m2, &m3)
}
In your case, using Zero is equivalent to m3 in the example above. Using New is equivalent to m2.
I've found the way how to do what i need
val := reflect.New(reflect.TypeOf(Message{}))
json.Unmarshal(data, val.Interface())
return val.Elem().Interface()
http://play.golang.org/p/8g9FSg3MSj
But was was wrong wit the first version???
It Looks like reflect.Zero(type) should be equivalent to reflect.New(type).Elem() - am i wrong?

Catchall type for Golang API response

I am trying to define a struct that can hold an array of any type like so:
type APIResonse struct {
length int
data []interface{}
}
I want the data property to be capable of holding an array of any type/struct so I can have a single response type, that will eventually be serialized to json. So what I want to be able to write is something like the following:
someStruct := getSomeStructArray()
res := &APIResponse{
length: len(someStruct),
data: someStruct,
}
enc, err := json.Marshal(res)
Is this possible in Go? I keep getting cannot use cs (type SomeType) as type []interface {} in assignment. Or do I have to create a different response type for every variation of data? Or maybe I am going about this wrong entirely / not Go-like. Any help would be much appreciated!
There are a couple of problems with that code.
You need to use interface{}, not []interface{}, also [] is called a slice, an array is a fixed number of elements like [10]string.
And your APIResponse fields aren't exported, so json.Marshal will not print out anything.
func main() {
d := []dummy{{100}, {200}}
res := &APIResponse{
Length: len(d),
Data: d,
}
enc, err := json.Marshal(res)
fmt.Println(string(enc), err)
}
playground