JSON Decoding using Go fails - json

i have some problems decoding the body of a http response. The response I get from using Insomnia looks like this:
[
{
"name": "monitoring",
"instances": [
{
"host": "ite00716.local",
"id": "2058b934-720f-47c5-a1da-3d1535423b83",
"port": 8080
}
]
},
{
"name": "app1",
"instances": [
{
"host": "172.20.10.2",
"id": "bc9a5859-8dda-418a-a323-11f67fbe1a71",
"port": 8081
}
]
}
]
When I use the following go code, the struct I decode to is empty. I'm not sure why. Please help me!
type Service struct {
Name string `json:"name"`
Instances []Instance `json:"instances"`
}
type Instance struct {
Host string `json:"host"`
Id string `json:"id"`
Port int `json:"port"`
}
func main() {
resp, err := http.Get("http://localhost:8080/services")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var s Service
json.NewDecoder(resp.Body).Decode(&s)
fmt.Println(s)
}

Your json response is array of service
var s []Service

The problem may be that your variable is a Service, while your json represent an array of "Service"s.
Try declaring s as:
var s []Service;

Related

Parse AWS ec2 DescribeInstanceStatusOutput JSON response using Golang

Hi all I am a beginner trying to learn Go. I am trying to get the status of my EC2 instance using AWS SDK with Golang. I was successfully able to get a JSON response for my instance. I am however, having issues trying to parse the JSON response to get the instance state.
The following is how I get the JSON response:
result, err := ec2Svc.DescribeInstanceStatus(input)
where input is of type ec2.DescribeInstanceStatusInput
The following is the JSON response I get:
{
"InstanceStatuses": [
{
"AvailabilityZone": "ap-southeast-2b",
"Events": null,
"InstanceId": "[VALIDIMAGEID]",
"InstanceState": {
"Code": 16,
"Name": "running"
},
"InstanceStatus": {
"Details": [
{
"ImpairedSince": null,
"Name": "reachability",
"Status": "passed"
}
],
"Status": "ok"
},
"OutpostArn": null,
"SystemStatus": {
"Details": [
{
"ImpairedSince": null,
"Name": "reachability",
"Status": "passed"
}
],
"Status": "ok"
}
}
],
"NextToken": null
}
I then parse the result which is of type DescribeInstanceStatusOutput to String and then pass it to the function parseJson where I attempt to parse the JSON.
result, err := ec2Svc.DescribeInstanceStatus(input)
if err != nil {
fmt.Println("Error", err)
} else {
parseJson(result.String())
//fmt.Printf("%v\n", result)
}
func parseJson(ec2StatusDescription string){
//var x string = ec2StatusDescription.String()
//fmt.Print(ec2StatusDescription)
data := []byte(ec2StatusDescription)
var instanceOutputs InstanceOutput
json.Unmarshal(data, &instanceOutputs)
fmt.Print(instanceOutputs.InstanceStatuses[0].InstanceState.Name)
}
type InstanceOutput struct {
InstanceStatuses [] InstanceStatuses
NextToken *string
}
type InstanceStatuses struct {
AvailabilityZone string
Events *string
InstanceId string
InstanceState InstanceState
InstanceStatus InstanceStatus
OutpostArn *string
SystemStatus SystemStatus
}
type InstanceState struct {
Code int
Name string
}
type InstanceStatus struct {
Details [] InstanceDetails
Status string
}
type InstanceDetails struct {
ImpairedSince *string
Name string
Status string
}
type SystemStatus struct {
Details [] InstanceDetails
Status string
}
I am trying to print the instance state but I keep getting index out of range, and this is because the JSON is not being parsed properly. What am i missing here that's causing the json to not parse correctly? Or am I getting it completely wrong in what I'm doing here? Any help would be highly appreciated.

Parse json body array sent in POST request and print

I am having one issue while reading the json array. Need help for below query.
Request Json :
{ "httpReq": {
"username": "1234567890",
"password": "1234567890",
"number": "123456"
}
}
Response Json :
{ "httpResp": {
"status": "Pass",
"message": "great"
}
}
Below was my code: If i am passing the json object below its working, but i need to send "httpReq" in json.
package main
import (
"encoding/json"
"fmt"
)
type people struct {
Username string `json:"username"`
Password string `json:"password"`
Number string `json:"number"`
}
type peopleread struct {
Collection []people
}
func main() {
text := `{
"username": "1234567890",
"password": "1234567890",
"number": "123456"
}`
textBytes := []byte(text)
//people1 := people{}
var people2 people
err := json.Unmarshal(textBytes, &people2)
if err != nil {
fmt.Println(err)
return
}
Username := people2.Username
Password := people2.Password
Number := people2.Number
fmt.Println(Username)
fmt.Println(Password)
fmt.Println(Number)
}
To unmarshal with httpReq field you have to handle this.
Create a struct to your wrap your request body like json
type HttpReq struct{
HttpReq people `json:"httpReq"`
}
Then use unmarshal
var httpReq HttpReq
err := json.Unmarshal(textBytes, &httpReq)
Full code in Go playground here

Unmarshal JSON tagged union in Go

I'm trying to demarshal the JSON requests of Google Actions. These have arrays of tagged unions like this:
{
"requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
"inputs": [{
"intent": "action.devices.QUERY",
"payload": {
"devices": [{
"id": "123",
"customData": {
"fooValue": 74,
"barValue": true,
"bazValue": "foo"
}
}, {
"id": "456",
"customData": {
"fooValue": 12,
"barValue": false,
"bazValue": "bar"
}
}]
}
}]
}
{
"requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
"inputs": [{
"intent": "action.devices.EXECUTE",
"payload": {
"commands": [{
"devices": [{
"id": "123",
"customData": {
"fooValue": 74,
"barValue": true,
"bazValue": "sheepdip"
}
}, {
"id": "456",
"customData": {
"fooValue": 36,
"barValue": false,
"bazValue": "moarsheep"
}
}],
"execution": [{
"command": "action.devices.commands.OnOff",
"params": {
"on": true
}
}]
}]
}
}]
}
etc.
Obviously I can demarshal this to an interface{} and use fully dynamic type casts and everything to decode it, but Go has decent support for decoding to structs. Is there a way to do this elegantly in Go (like you can in Rust for example)?
I feel like you could almost do it by reading demarshalling to this initially:
type Request struct {
RequestId string
Inputs []struct {
Intent string
Payload interface{}
}
}
However once you have the Payload interface{} there doesn't seem to be any way to deserialise that into a struct (other than serialising it and deserialising it again which sucks. Is there any good solution?
Instead of unmarshaling Payload to an interface{} you can store it as a json.RawMessage and then unmarshal it based on the value of Intent. This is shown in the example in the json docs:
https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal
Using that example with your JSON and struct your code becomes something like this:
type Request struct {
RequestId string
Inputs []struct {
Intent string
Payload json.RawMessage
}
}
var request Request
err := json.Unmarshal(j, &request)
if err != nil {
log.Fatalln("error:", err)
}
for _, input := range request.Inputs {
var payload interface{}
switch input.Intent {
case "action.devices.EXECUTE":
payload = new(Execute)
case "action.devices.QUERY":
payload = new(Query)
}
err := json.Unmarshal(input.Payload, payload)
if err != nil {
log.Fatalln("error:", err)
}
// Do stuff with payload
}
I made https://github.com/byrnedo/pjson for exactly this reason.
So in your case you'd have:
type Input interface {
// Variant func is required
Variant() string
}
type ExecuteInput struct {
Payload struct {
// is any just to avoid typing it for this example
Commands []any `json:"commands"`
} `json:"payload"`
}
func (q ExecuteInput) Variant() string {
return "action.devices.EXECUTE"
}
type QueryInput struct {
Payload struct {
// is any just to avoid typing it for this example
Devices []any `json:"devices"`
} `json:"payload"`
}
func (q QueryInput) Variant() string {
return "action.devices.QUERY"
}
type Inputs []Input
func (i Inputs) MarshalJSON() ([]byte, error) {
return pjson.New(Inputs{}, pjson.WithVariantField("intent")).MarshalArray(i)
}
func (i *Inputs) UnmarshalJSON(bytes []byte) (err error) {
*i, err = pjson.New(Inputs{ExecuteInput{}, QueryInput{}}, pjson.WithVariantField("intent")).UnmarshalArray(bytes)
return
}
type Request struct {
RequestId string `json:"requestId"`
Inputs Inputs `json:"inputs"`
}
Try it on go playground

Unmarshalling complex json in Go

So I am trying to fetch the analytics of an app by pinging and endpoint. I make the GET request which is successfull (no errors there) but I am unable to decode the JSON
I need to to decode the following json into structs
{
"noResultSearches": {
"results": [
{
"count": 1,
"key": "\"note 9\""
},
{
"count": 1,
"key": "nokia"
}
]
},
"popularSearches": {
"results": [
{
"count": 4,
"key": "6"
},
{
"count": 2,
"key": "\"note 9\""
},
{
"count": 1,
"key": "nokia"
}
]
},
"searchVolume": {
"results": [
{
"count": 7,
"key": 1537401600000,
"key_as_string": "2018/09/20 00:00:00"
}
]
}
}
For which I am using the following structs
type analyticsResults struct {
Count int `json:"count"`
Key string `json:"key"`
}
type analyticsVolumeResults struct {
Count int `json:"count"`
Key int64 `json:"key"`
DateAsStr string `json:"key_as_string"`
}
type analyticsPopularSearches struct {
Results []analyticsResults `json:"results"`
}
type analyticsNoResultSearches struct {
Results []analyticsResults `json:"results"`
}
type analyticsSearchVolume struct {
Results []analyticsVolumeResults `json:"results"`
}
type overviewAnalyticsBody struct {
NoResultSearches analyticsNoResultSearches `json:"noResultSearches"`
PopularSearches analyticsPopularSearches `json:"popularSearches"`
SearchVolume analyticsSearchVolume `json:"searchVolume"`
}
I make a GET request to an endpoint and then use the response body to decode the json but I get an error. Following is a part of the code that stays in my ShowAnalytics function
func ShowAppAnalytics(app string) error {
spinner.StartText("Fetching app analytics")
defer spinner.Stop()
fmt.Println()
req, err := http.NewRequest("GET", "<some-endpoint>", nil)
if err != nil {
return err
}
resp, err := session.SendRequest(req)
if err != nil {
return err
}
spinner.Stop()
var res overviewAnalyticsBody
dec := json.NewDecoder(resp.Body)
err = dec.Decode(&res)
if err != nil {
return err
}
fmt.Println(res)
return nil
}
json: cannot unmarshal array into Go struct field
overviewAnalyticsBody.noResultSearches of type
app.analyticsNoResultSearches
What am I doing wrong here? Why do I get this error?
EDIT: After you edited, your current code works as-is. Check it out here: Go Playground.
Original answer follows.
There is some inconsistency between the code you posted and the error you get.
I tried it on the Go Playground (here's your version), and I get the following error:
json: cannot unmarshal number into Go struct field analyticsVolumeResults.key of type string
We get this error because in the JSON searchVolume.results.key is a number:
"key": 1537401600000,
And you used string in the Go model:
Key string `json:"key"`
If we change it to int64:
Key int64 `json:"key"`
It works, and prints (try it on the Go Playground):
{{[{1 "note 9"} {1 nokia}]} {[{4 6} {2 "note 9"} {1 nokia}]} {[{7 1537401600000 2018/09/20 00:00:00}]}}
If that key may sometimes be a number and sometimes a string, you may also use json.Number in the Go model:
Key json.Number `json:"key"`

How to decode json into structs

I'm trying to decode some json in Go but some fields don't get decoded.
See the code running in browser here:
What am I doing wrong?
I need only the MX records so I didn't define the other fields. As I understand from the godoc you don't need to define the fields you don't use/need.
// You can edit this code!
// Click here and start typing.
package main
import "fmt"
import "encoding/json"
func main() {
body := `
{"response": {
"status": "SUCCESS",
"data": {
"mxRecords": [
{
"value": "us2.mx3.mailhostbox.com.",
"ttl": 1,
"priority": 100,
"hostName": "#"
},
{
"value": "us2.mx1.mailhostbox.com.",
"ttl": 1,
"priority": 100,
"hostName": "#"
},
{
"value": "us2.mx2.mailhostbox.com.",
"ttl": 1,
"priority": 100,
"hostName": "#"
}
],
"cnameRecords": [
{
"aliasHost": "pop.a.co.uk.",
"canonicalHost": "us2.pop.mailhostbox.com."
},
{
"aliasHost": "webmail.a.co.uk.",
"canonicalHost": "us2.webmail.mailhostbox.com."
},
{
"aliasHost": "smtp.a.co.uk.",
"canonicalHost": "us2.smtp.mailhostbox.com."
},
{
"aliasHost": "imap.a.co.uk.",
"canonicalHost": "us2.imap.mailhostbox.com."
}
],
"dkimTxtRecord": {
"domainname": "20a19._domainkey.a.co.uk",
"value": "\"v=DKIM1; g=*; k=rsa; p=DkfbhO8Oyy0E1WyUWwIDAQAB\"",
"ttl": 1
},
"spfTxtRecord": {
"domainname": "a.co.uk",
"value": "\"v=spf1 redirect=_spf.mailhostbox.com\"",
"ttl": 1
},
"loginUrl": "us2.cp.mailhostbox.com"
}
}}`
type MxRecords struct {
value string
ttl int
priority int
hostName string
}
type Data struct {
mxRecords []MxRecords
}
type Response struct {
Status string `json:"status"`
Data Data `json:"data"`
}
type apiR struct {
Response Response
}
var r apiR
err := json.Unmarshal([]byte(body), &r)
if err != nil {
fmt.Printf("err was %v", err)
}
fmt.Printf("decoded is %v", r)
}
As per the go documentaiton about json.Unmarshal, you can only decode toward exported fields, the main reason being that external packages (such as encoding/json) cannot acces unexported fields.
If your json doesn't follow the go convention for names, you can use the json tag in your fields to change the matching between json key and struct field.
Exemple:
package main
import (
"fmt"
"encoding/json"
)
type T struct {
Foo string `json:"foo"`
priv string `json:"priv"`
}
func main() {
text := []byte(`{"foo":"bar", "priv":"nothing"}`)
var t T
err := json.Unmarshal(text, &t)
if err != nil {
panic(err)
}
fmt.Println(t.Foo) // prints "bar"
fmt.Println(t.priv) // prints "", priv is not exported
}
You must Uppercase struct fields:
type MxRecords struct {
Value string `json:"value"`
Ttl int `json:"ttl"`
Priority int `json:"priority"`
HostName string `json:"hostName"`
}
type Data struct {
MxRecords []MxRecords `json:"mxRecords"`
}
http://play.golang.org/p/EEyiISdoaE
The encoding/json package can only decode into exported struct fields. Your Data.mxRecords member is not exported, so it is ignored when decoding. If you rename it to use a capital letter, the JSON package will notice it.
You will need to do the same thing for all the members of your MxRecords type.