Not able to process json string data in golang struct - json

I have a json data in string (coming from third party API). I am not able to decode json string data in golang.
Please help.
JSON string =
{
"data" : {
"additional-30": {
"id_sales_rule_set": 255626,
"voucher_code": "PR35ZR5J5",
"from_date": "2015-06-16 16:19:22",
"to_date": "2018-09-28 23:59:59",
"conditions_ruleset": {
"subTotal": 0,
"category": {},
"customer": "0",
"paymentMethod": null,
"capOnDiscount": null,
"skuExclude": null,
"discountedItem": 0,
"discounted": 1500,
"taggedItem": null,
"segmentedVoucher": null,
"bundle": null,
"brand": null,
"mobileVoucher": null,
"itemAttribute": {}
},
"discount_type": "fixed",
"discount_percentage": null,
"discount_amount_default": 500
},
"abcd": {
"id_sales_rule_set": 255626,
"voucher_code": "PR35ZR5J5",
"from_date": "2015-06-16 16:19:22",
"to_date": "2018-09-28 23:59:59",
"conditions_ruleset": {
"subTotal": 0,
"category": {},
"customer": "0",
"paymentMethod": null,
"capOnDiscount": null,
"skuExclude": null,
"discountedItem": 0,
"discounted": 1500,
"taggedItem": null,
"segmentedVoucher": null,
"bundle": null,
"brand": null,
"mobileVoucher": null,
"itemAttribute": {}
},
"discount_type": "fixed",
"discount_percentage": null,
"discount_amount_default": 500
}
}
}
Struct in which I want to get data
type ConditionsRuleset struct {
Brand interface{} `json:"brand"`
Bundle interface{} `json:"bundle"`
CapOnDiscount interface{} `json:"capOnDiscount"`
Category struct{} `json:"category"`
Customer string `json:"customer"`
Discounted int `json:"discounted"`
DiscountedItem int `json:"discountedItem"`
ItemAttribute struct{} `json:"itemAttribute"`
MobileVoucher interface{} `json:"mobileVoucher"`
PaymentMethod interface{} `json:"paymentMethod"`
SegmentedVoucher interface{} `json:"segmentedVoucher"`
SkuExclude interface{} `json:"skuExclude"`
SubTotal int `json:"subTotal"`
TaggedItem interface{} `json:"taggedItem"`
}
type PromoVoucher struct {
ConditionsRuleset ConditionsRuleset `json:"conditions_ruleset"`
DiscountAmountDefault int `json:"discount_amount_default"`
DiscountPercentage interface{} `json:"discount_percentage"`
DiscountType string `json:"discount_type"`
FromDate string `json:"from_date"`
IDSalesRuleSet int `json:"id_sales_rule_set"`
ToDate string `json:"to_date"`
VoucherCode string `json:"voucher_code"`
}
type PromoCacheData struct {
Data map[string]interface{} `json:"data"`
}
Here is my code where I want to process json
by := []byte(<json string>)
tmp := new(PromoCacheData)
json.Unmarshal(by,tmp)
for k,value := range *tmp {
byc, _ := json.Marshal(value)
tmp2 := new(PromoVoucher)
json.Unmarshal(byc,tmp2)
fmt.Println(tmp2)
}
Error I am getting : cannot range over *tmp (type PromoCacheData)

You need to use tmp.Data in for loop instead of *tmp.
Error message says that exactly

Related

Is there an "any" tag for json unmarshal?

I want to make two calls to coinmarketcap, and the response is almost the same for both calls, only differs one ID
If I call with id="1" then the repsonse struct will be somenthing like this
{
"status": {
"timestamp": "2022-01-31T11:08:10.552Z",
"error_code": 0,
"error_message": null,
"elapsed": 29,
"credit_count": 1,
"notice": null
},
"data": {
"1": {
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"slug": "bitcoin",
"num_market_pairs": 9121,
"date_added": "2013-04-28T00:00:00.000Z",
"max_supply": 21000000,
[...]
"last_updated": "2022-01-31T11:06:00.000Z",
"quote": {
"USD": {
"price": 37287.959833145724,
"volume_24h": 16426509863.271738,
"volume_change_24h": -5.6098,
"percent_change_1h": 0.18350099,
"percent_change_24h": -2.27056162,
"percent_change_7d": 11.98926671,
"percent_change_30d": -20.53627257,
"percent_change_60d": -33.95545743,
"percent_change_90d": -40.97732382,
"market_cap": 706414097373.7339,
"market_cap_dominance": 41.8173,
"fully_diluted_market_cap": 783047156496.06,
"last_updated": "2022-01-31T11:06:00.000Z"
}
}
}
}
}
When I make the same api call using id="1027" then the response changes its structure
{
"status": {
"timestamp": "2022-01-31T11:46:02.894Z",
"error_code": 0,
"error_message": null,
"elapsed": 28,
"credit_count": 1,
"notice": null
},
"data": {
"1027": {
"id": 1027,
"name": "Ethereum",
"symbol": "ETH",
"slug": "ethereum",
"num_market_pairs": 5482,
"date_added": "2015-08-07T00:00:00.000Z",
"max_supply": null,
[...]
"last_updated": "2022-01-31T11:44:00.000Z",
"quote": {
"USD": {
"price": 2535.692637309123,
"volume_24h": 10427616453.128471,
"volume_change_24h": -6.6085,
"percent_change_1h": -0.23965775,
"percent_change_24h": -3.07033246,
"percent_change_7d": 12.35705047,
"percent_change_30d": -31.64459631,
"percent_change_60d": -44.12893821,
"percent_change_90d": -42.93608624,
"market_cap": 302724966788.3792,
"market_cap_dominance": 17.9889,
"fully_diluted_market_cap": 302724966788.38,
"last_updated": "2022-01-31T11:44:00.000Z"
}
}
}
}
}
Is there any possibility of using json:"*any" when declaring the struct ?
Now, if I'm declaring it with 1 (json:"1") it only works for BTC
type CoinInfoResponse struct {
Status struct {
Timestamp time.Time `json:"timestamp"`
ErrorCode int `json:"error_code"`
ErrorMessage interface{} `json:"error_message"`
Elapsed int `json:"elapsed"`
CreditCount int `json:"credit_count"`
Notice interface{} `json:"notice"`
} `json:"status"`
Data struct {
Coin struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Slug string `json:"slug"`
NumMarketPairs int `json:"num_market_pairs"`
DateAdded time.Time `json:"date_added"`
Tags []struct {
Slug string `json:"slug"`
Name string `json:"name"`
Category string `json:"category"`
} `json:"tags"`
MaxSupply interface{} `json:"max_supply"`
CirculatingSupply float64 `json:"circulating_supply"`
TotalSupply float64 `json:"total_supply"`
Platform struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Slug string `json:"slug"`
TokenAddress string `json:"token_address"`
} `json:"platform"`
IsActive int `json:"is_active"`
CmcRank int `json:"cmc_rank"`
IsFiat int `json:"is_fiat"`
LastUpdated time.Time `json:"last_updated"`
Quote struct {
USD struct {
Price float64 `json:"price"`
Volume24H float64 `json:"volume_24h"`
PercentChange1H float64 `json:"percent_change_1h"`
PercentChange24H float64 `json:"percent_change_24h"`
PercentChange7D float64 `json:"percent_change_7d"`
PercentChange30D float64 `json:"percent_change_30d"`
PercentChange60D float64 `json:"percent_change_60d"`
PercentChange90D float64 `json:"percent_change_90d"`
MarketCap float64 `json:"market_cap"`
LastUpdated time.Time `json:"last_updated"`
} `json:"USD"`
} `json:"quote"`
} `json:"1"`
} `json:"data"`
}
You need a custom unmarshaler. It should parse "ID" first, then to unmarshal internal {...} object.
Example (https://go.dev/play/p/co12qwKBFpc):
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"strings"
)
const input = `
{
"status": {
"error_code": 100
},
"data": {
"12345": {
"id": 12345,
"name": "aaaaa",
"symbol": "bbbb"
}
}
}`
type data struct {
TagName string
Coin struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
// other fileds skipped
}
}
func (d *data) UnmarshalJSON(b []byte) error {
idx := bytes.Index(b, []byte(":"))
// cut "number" and put into TagName
d.TagName = strings.Trim(string(b[:idx-1]), "{\":\n ")
// unmarshal internal {} object
return json.Unmarshal(b[idx+1:len(b)-1], &d.Coin)
}
type Response struct {
Status struct {
ErrorCode int `json:"error_code"`
} `json:"status"`
Data data
}
func main() {
var resp Response
if err := json.Unmarshal([]byte(input), &resp); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", resp)
}

Trying to parse the JSON and create an extracted JSON

I am trying to create a JSON object on the fly based on the data received from the API.
Sample Data received: Unmarshalled the data into CiItems struct given below
{
"class_name": "test",
"configuration_items": [
{
"id": "ea09a24f-01ef-42ad-ab19-e0369341d9b3",
"ci_name": "makk",
"comments": null,
"created_by": "mike",
"updated_by": "sam",
"created": "2019-08-02T21:16:35.656Z",
"updated": "2019-08-02T21:21:08.073Z",
"ci_state_id": "randomid",
"super_ci_id": null,
"ci_attributes": [
{
"attribute_id": "c995c693-b97c-4863-a61b-81a5d904c967",
"df_attribute_value": "xsmall",
"attribute_name": "tname",
"data_type": "string"
},
{
"attribute_id": "58845f48-7d2a-4c8c-8591-eaf59a23d84d",
"df_attribute_value": "vmware",
"attribute_name": "provider",
"data_type": "string"
}
]}]}
Below are the structs created:
type Attribute struct {
AttributeID string `json:"attribute_id "`
DfAttributeValue string `json:"df_attribute_value"`
AttName string `json:"attribute_name"`
DataType string `json:"data_type"`
}
// Attributes - array of Attribute
type Attributes []Attribute
// CiItem - confiuraion item of a VM
type CiItem struct {
ID string `json:"ci_id"`
Created string `json:"created"`
Updated string `json:"updated"`
CreatedBY string `json:"created_by"`
UpdatedBY string `json:"updated_by"`
Atts Attributes `json:"ci_attributes"`
}
// CiItems - array of CiItem
type CiItems struct {
ClassName string `json:"class_name"`
Items []CiItem `json:"configuration_items"`
}
Code to unmarshal the data and create a extracted Json:
func (client *Client) GetList() (CiItems, error) {
var out CiItems
err := client.doJsonRequest("GET", &out)
log.Info(out)
if err != nil {
return out, err
}
var output map[string]interface{}
//parseMap(out.Items[0].(map[string]interface{}))
extractBase(out.Items[0], &output)
return out, nil
}
func extractBase(ci interface{}, output interface{}) {
fields := reflect.TypeOf(ci)
values := reflect.ValueOf(ci)
num := fields.NumField()
for i := 0; i < num; i++ {
field := fields.Field(i)
value := values.Field(i)
if string(field.Name) != "Atts" {
name := string(field.Name)
output[name] = string(value)
}
}
}
I am trying to create a JSON with key value of id, ci_name, created_by, updated_by, attribute_name as below
{
"ci_name": "makk",
"created_by": "mike",
"updated_by": "sam",
"created": "2019-08-02T21:16:35.656Z",
"updated": "2019-08-02T21:21:08.073Z",
"tname": "xsmall",
"provider": "vmware"
}
I have tried using reflect and other methods
Create a map using values from CiItem fields and return it from the function.
func extractBase(ci *CiItem) map[string]interface{} {
result := map[string]interface{}{
"ci_name": ci.Name,
"created_by": ci.CreatedBY,
"updated_by": ci.UpdatedBY,
"created": ci.Created,
"updated": ci.Updated,
}
for _, a := range ci.Atts {
result[a.AttName] = a.DfAttributeValue
}
return result
}

Why can this data not be properly unmarshalled into my object model?

I have a (non)working example here: https://play.golang.org/p/qaYhKvJ65J3
I'm not sure why the following data:
alertData := `{
"Id": 0,
"Version": 0,
"OrgId": 1,
"DashboardId": 61,
"PanelId": 84,
"Name": "{qa-dev}{stats-pipeline} Topology Message Age (aggregator) alert",
"Message": "",
"Severity": "",
"State": "",
"Handler": 1,
"Silenced": false,
"ExecutionError": "",
"Frequency": 10,
"EvalData": null,
"NewStateDate": "0001-01-01T00:00:00Z",
"PrevStateDate": "0001-01-01T00:00:00Z",
"StateChanges": 0,
"Created": "0001-01-01T00:00:00Z",
"Updated": "0001-01-01T00:00:00Z",
"Settings": {
"conditions": [
{
"evaluator": {
"params": [
10000
],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"datasourceId": 2,
"model": {
"hide": true,
"refCount": 0,
"refId": "C",
"textEditor": false
},
"params": [
"C",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"executionErrorState": "keep_state",
"frequency": "10s",
"handler": 1,
"name": "{qa-dev}{stats-pipeline} Topology Message Age (aggregator) alert",
"noDataState": "keep_state",
"notifications": []
}
}`
Can't be unmarshalled into the following object model:
type Condition struct {
Evaluator struct {
Params []int `json:"params"`
Type string `json:"type"`
} `json:"evaluator"`
Operator struct {
Type string `json:"type"`
} `json:"operator"`
Query struct {
Params []string `json:"params"`
} `json:"query"`
Reducer struct {
Params []interface{} `json:"params"`
Type string `json:"type"`
} `json:"reducer"`
Type string `json:"type"`
}
When I do the following:
condition := Condition{}
err := json.Unmarshal([]byte(alertData), &condition)
if err != nil {
panic(err)
}
fmt.Printf("\n\n json object:::: %+v", condition)
I just get: json object:::: {Evaluator:{Params:[] Type:} Operator:{Type:} Query:{Params:[]} Reducer:{Params:[] Type:} Type:}
Ideally I'd be able to parse it into something like type Conditions []struct{ } but I'm not sure if you can define models as lists?
It looks like you are trying to access the "conditions" property nested under the root "Settings" property. As such, you need to define that root-level type and enough fields to tell the unmarshaler how to find your target property. As such, you would just need to create a new "AlertData" type with the necessary "Settings/conditions" fields.
For example (Go Playground):
type AlertData struct {
Settings struct {
Conditions []Condition `json:"conditions"`
}
}
func main() {
alert := AlertData{}
err := json.Unmarshal([]byte(alertData), &alert)
if err != nil {
panic(err)
}
fmt.Printf("OK: conditions=%#v\n", alert.Settings.Conditions)
// OK: conditions=[]main.Condition{main.Condition{Evaluator:struct { Params []int "json:\"params\""; Type string "json:\"type\"" }{Params:[]int{10000}, Type:"gt"}, Operator:struct { Type string "json:\"type\"" }{Type:"and"}, Query:struct { Params []string "json:\"params\"" }{Params:[]string{"C", "5m", "now"}}, Reducer:struct { Params []interface {} "json:\"params\""; Type string "json:\"type\"" }{Params:[]interface {}{}, Type:"avg"}, Type:"query"}}
}
Note that the printed listing includes so much type information because the "Condition" type uses anonymous structs as field types. If you were to extract them into named structs it will be easier to work with the data, e.g.:
type Condition struct {
Evaluator Evaluator `json:"evaluator"`
Operator Operator `json:"operator"`
// ...
}
type Evaluator struct {
Params []int `json:"params"`
Type string `json:"type"`
}
type Operator struct {
Type string `json:"type"`
}
//...
// OK: conditions=[]main.Condition{
// main.Condition{
// Evaluator:main.Evaluator{Params:[]int{10000}, Type:"gt"},
// Operator:main.Operator{Type:"and"},
// Query:main.Query{Params:[]string{"C", "5m", "now"}},
// Reducer:main.Reducer{Params:[]interface {}{}, Type:"avg"},
// Type:"query",
// },
// }
Go Playground example here...
Maerics explanation is correct, here is an alternative approach which wraps access around struct methods, the data structure is also fully defined. If you're new to Go it's good to get handle on creating the data structures yourself, but here is a handy utility for helping create structs from valid JSON
https://mholt.github.io/json-to-go/
package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
type Data struct {
ID int `json:"Id"`
Version int `json:"Version"`
OrgID int `json:"OrgId"`
DashboardID int `json:"DashboardId"`
PanelID int `json:"PanelId"`
Name string `json:"Name"`
Message string `json:"Message"`
Severity string `json:"Severity"`
State string `json:"State"`
Handler int `json:"Handler"`
Silenced bool `json:"Silenced"`
ExecutionError string `json:"ExecutionError"`
Frequency int `json:"Frequency"`
EvalData interface{} `json:"EvalData"`
NewStateDate time.Time `json:"NewStateDate"`
PrevStateDate time.Time `json:"PrevStateDate"`
StateChanges int `json:"StateChanges"`
Created time.Time `json:"Created"`
Updated time.Time `json:"Updated"`
Settings struct {
Conditions []Condition `json:"conditions"`
ExecutionErrorState string `json:"executionErrorState"`
Frequency string `json:"frequency"`
Handler int `json:"handler"`
Name string `json:"name"`
NoDataState string `json:"noDataState"`
Notifications []interface{} `json:"notifications"`
} `json:"Settings"`
}
type Condition struct {
Evaluator struct {
Params []int `json:"params"`
Type string `json:"type"`
} `json:"evaluator"`
Operator struct {
Type string `json:"type"`
} `json:"operator"`
Query struct {
DatasourceID int `json:"datasourceId"`
Model struct {
Hide bool `json:"hide"`
RefCount int `json:"refCount"`
RefID string `json:"refId"`
TextEditor bool `json:"textEditor"`
} `json:"model"`
Params []string `json:"params"`
} `json:"query"`
Reducer struct {
Params []interface{} `json:"params"`
Type string `json:"type"`
} `json:"reducer"`
Type string `json:"type"`
}
func (d Data) GetFirstCondition() (Condition, error) {
if len(d.Settings.Conditions) > 0 {
return d.Settings.Conditions[0], nil
}
return Condition{}, fmt.Errorf("no conditions found")
}
func (d Data) GetConditionByIndex(index uint) (Condition, error) {
if len(d.Settings.Conditions) == 0 {
return Condition{}, fmt.Errorf("no conditions found")
}
if int(index) > len(d.Settings.Conditions)-1 {
return Condition{}, fmt.Errorf("index out of bounds")
}
return d.Settings.Conditions[index], nil
}
var alertData = `{
"Id": 0,
"Version": 0,
"OrgId": 1,
"DashboardId": 61,
"PanelId": 84,
"Name": "{qa-dev}{stats-pipeline} Topology Message Age (aggregator) alert",
"Message": "",
"Severity": "",
"State": "",
"Handler": 1,
"Silenced": false,
"ExecutionError": "",
"Frequency": 10,
"EvalData": null,
"NewStateDate": "0001-01-01T00:00:00Z",
"PrevStateDate": "0001-01-01T00:00:00Z",
"StateChanges": 0,
"Created": "0001-01-01T00:00:00Z",
"Updated": "0001-01-01T00:00:00Z",
"Settings": {
"conditions": [
{
"evaluator": {
"params": [
10000
],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"datasourceId": 2,
"model": {
"hide": true,
"refCount": 0,
"refId": "C",
"textEditor": false
},
"params": [
"C",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"executionErrorState": "keep_state",
"frequency": "10s",
"handler": 1,
"name": "{qa-dev}{stats-pipeline} Topology Message Age (aggregator) alert",
"noDataState": "keep_state",
"notifications": []
}
}`
func main() {
var res Data
err := json.Unmarshal([]byte(alertData), &res)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.GetFirstCondition())
fmt.Println(res.GetConditionByIndex(0))
// should fail :-)
fmt.Println(res.GetConditionByIndex(1))
}

Dynamic JSON struct, API result golang

I have to make two HTTP API calls in GoLang, the first API call returns this json response:
{
"status": 200,
"msg": "OK",
"result": {
"id": "24",
"folderid": "4248"
}
}
My json struct for first response is setup like this:
type One struct {
Status int `json:"status"`
Msg string `json:"msg"`
Result struct {
ID string `json:"id"`
Folderid string `json:"folderid"`
} `json:"result"`
}
The second call is where the problem is. As you can see the first API call returns a result -> id. This ID should be the name for the beginning of my second struct, but I can't seem how to make it dynamic or put a result as my structure name. This ID (24) will always change based on the first API call. I have no way currently to parse the second call's JSON and setup my struct. On the second API call I want to access the remoteurl/status.
Second call result (I can not parse):
{
"status": 200,
"msg": "OK",
"result": {
24: ** THIS IS DYNAMIC** {
"id": 24,
"remoteurl": "http://proof.ovh.net/files/100Mio.dat",
"status": "new",
"bytes_loaded": null,
"bytes_total": null,
"folderid": "4248",
"added": "2015-02-21 09:20:26",
"last_update": "2015-02-21 09:20:26",
"extid": false,
"url": false
}
}
}
Does anyone know how to setup my struct or go about this. I am a new programmer and go and have worked on this for 4 days. And decided to ask for some help, since I am in school and have normal homework.
Found that using JSON-to-GO helped solve future problems, will create the structs and other necessities based off a JSON content.
{
"status": 200,
"msg": "OK",
"result": {
24: {
"id": 24,
"remoteurl": "http://proof.ovh.net/files/100Mio.dat",
"status": "new",
"bytes_loaded": null,
"bytes_total": null,
"folderid": "4248",
"added": "2015-02-21 09:20:26",
"last_update": "2015-02-21 09:20:26",
"extid": false,
"url": false
}
}
}
Is not value JSON. You MUST mean the JSON i posted below, if you want to check yourself, copy your version of the JSON into any JSON validator;
https://jsonlint.com/
https://jsoneditoronline.org/
https://jsonformatter.curiousconcept.com/
Also view the thread linked below.. if the API truly is returning what you claim it returns then the API has a bug in it
Why JSON allows only string to be a key?
{
"status": 200,
"msg": "OK",
"result": {
"24": {
"id": 24,
"remoteurl": "http://proof.ovh.net/files/100Mio.dat",
"status": "new",
"bytes_loaded": null,
"bytes_total": null,
"folderid": "4248",
"added": "2015-02-21 09:20:26",
"last_update": "2015-02-21 09:20:26",
"extid": false,
"url": false
}
}
}
Here is some example code that uses a map to a struct what solves the dynamic response of the second response
package main
import (
"encoding/json"
"fmt"
"log"
)
var res1 = `{
"status": 200,
"msg": "OK",
"result": {
"id": "24",
"folderid": "4248"
}
}`
var res2 = `{
"status": 200,
"msg": "OK",
"result": {
"24": {
"id": 24,
"remoteurl": "http://proof.ovh.net/files/100Mio.dat",
"status": "new",
"bytes_loaded": null,
"bytes_total": null,
"folderid": "4248",
"added": "2015-02-21 09:20:26",
"last_update": "2015-02-21 09:20:26",
"extid": false,
"url": false
}
}
}
`
type One struct {
Status int `json:"status"`
Msg string `json:"msg"`
Result struct {
ID string `json:"id"`
Folderid string `json:"folderid"`
} `json:"result"`
}
type Two struct {
Status int `json:"status"`
Msg string `json:"msg"`
Result map[string]innerData `json:"result"`
}
type innerData struct {
ID int `json:"id"`
Remoteurl string `json:"remoteurl"`
Status string `json:"status"`
BytesLoaded interface{} `json:"bytes_loaded"`
BytesTotal interface{} `json:"bytes_total"`
Folderid string `json:"folderid"`
Added string `json:"added"`
LastUpdate string `json:"last_update"`
Extid bool `json:"extid"`
URL bool `json:"url"`
}
func main() {
var one One
err := json.Unmarshal([]byte(res1), &one)
if err != nil {
log.Fatal(err)
}
var two Two
err = json.Unmarshal([]byte(res2), &two)
if err != nil {
log.Fatal(err)
}
//pretty print both strutures
b, _ := json.MarshalIndent(one, "", " ")
fmt.Printf("%s \n\n", b)
b, _ = json.MarshalIndent(two, "", " ")
fmt.Printf("%s \n\n", b)
// access data from two with id from one
if dat, ok := two.Result[one.Result.ID]; ok {
b, _ = json.MarshalIndent(dat, "", " ")
fmt.Printf("inner data\n%s\n", b)
}
}

Unmarshal JSON with many layers of nested maps

I realize that this question is very similar to others on stackoverflow but I have not been able to model the other questions to my use case.
I have JSON that looks like this (simplified for this post)
{
"somekey": "string",
"state": {
"groups": {
"host:host1": {
"status": "OK",
"morethings": "blah"
},
"host:host2": {
"status": "Alert",
"morethings": "blah"
}
}
}
}
I am trying to get the hashes under groups into an array so I can iterate through and check the status of the hosts.
Based on some of the other posts here I felt I was on the correct track with this example:
package main
import (
"encoding/json"
"fmt"
)
const jsonStream = `
{
"state": {
"groups": {
"host:i-b3a6cea5": {
"status": "OK",
"last_triggered_ts": null,
"last_nodata_ts": null,
"name": "host:i-b3a6cea5",
"last_notified_ts": null,
"last_resolved_ts": null
},
"host:i-4d81ca7c": {
"status": "OK",
"last_triggered_ts": null,
"last_nodata_ts": null,
"name": "host:i-4d81ca7c",
"last_notified_ts": null,
"last_resolved_ts": null
},
"host:i-a03a7758": {
"status": "Alert",
"triggering_value": {
"to_ts": 1475092440,
"value": 2,
"from_ts": 1475092380
},
"last_triggered_ts": 1475092440,
"last_nodata_ts": null,
"name": "host:i-a03a7758",
"last_notified_ts": 1475092440,
"last_resolved_ts": null
}
}
}
}`
type hostDetails struct {
Status string `json:"status"`
Name string `json:"name"`
}
type GroupsData struct {
Groups map[string]hostDetails `json:"groups"`
}
type Data struct {
State map[string]GroupsData `json:"state"`
}
func main() {
var data Data
err := json.Unmarshal([]byte(jsonStream), &data)
if err != nil {
fmt.Println(err)
}
fmt.Println(data)
}
but I only end up with an empty data structure:
{map[groups:{map[]}]}
To see if I was even on the correct track I modified my JSON and took out the state key so that groups was the at the top level.
When I do that it populates the data structure as seen here
I'm struggling to understand why I can deal with the 1 level of nesting but not the second level?
My caveman brain thinks I should be able to reuse the pattern for as many levels of nesting I have.
At this point I've been fiddling most of the day and feel like I'm missing something that's right in front of me but can't see it.
Any pointers would be appreciated on how to handle the additional layer of nesting.
1 - You may remove one extra level by using:
var data map[string]GroupsData
Try it on The Go Playground:
package main
import (
"encoding/json"
"fmt"
)
func main() {
var data map[string]GroupsData
err := json.Unmarshal([]byte(jsonStream), &data)
if err != nil {
fmt.Println(err)
}
fmt.Println(data)
}
type GroupsData struct {
Groups map[string]HostDetails `json:"groups"`
}
type HostDetails struct {
Status string `json:"status"`
Name string `json:"name"`
}
const jsonStream = `
{
"state": {
"groups": {
"host:i-b3a6cea5": {
"status": "OK",
"last_triggered_ts": null,
"last_nodata_ts": null,
"name": "host:i-b3a6cea5",
"last_notified_ts": null,
"last_resolved_ts": null
},
"host:i-4d81ca7c": {
"status": "OK",
"last_triggered_ts": null,
"last_nodata_ts": null,
"name": "host:i-4d81ca7c",
"last_notified_ts": null,
"last_resolved_ts": null
},
"host:i-a03a7758": {
"status": "Alert",
"triggering_value": {
"to_ts": 1475092440,
"value": 2,
"from_ts": 1475092380
},
"last_triggered_ts": 1475092440,
"last_nodata_ts": null,
"name": "host:i-a03a7758",
"last_notified_ts": 1475092440,
"last_resolved_ts": null
}
}
}
}`
output:
map[state:{map[host:i-b3a6cea5:{OK host:i-b3a6cea5} host:i-4d81ca7c:{OK host:i-4d81ca7c} host:i-a03a7758:{Alert host:i-a03a7758}]}]
2 - You may use:
type Data struct {
State GroupsData `json:"state"`
}
Try it on The Go Playground:
package main
import (
"encoding/json"
"fmt"
)
func main() {
var data Data
err := json.Unmarshal([]byte(jsonStream), &data)
if err != nil {
fmt.Println(err)
}
fmt.Println(data)
}
type Data struct {
State GroupsData `json:"state"`
}
type GroupsData struct {
Groups map[string]hostDetails `json:"groups"`
}
type hostDetails struct {
Status string `json:"status"`
Name string `json:"name"`
}
const jsonStream = `
{
"state": {
"groups": {
"host:i-b3a6cea5": {
"status": "OK",
"last_triggered_ts": null,
"last_nodata_ts": null,
"name": "host:i-b3a6cea5",
"last_notified_ts": null,
"last_resolved_ts": null
},
"host:i-4d81ca7c": {
"status": "OK",
"last_triggered_ts": null,
"last_nodata_ts": null,
"name": "host:i-4d81ca7c",
"last_notified_ts": null,
"last_resolved_ts": null
},
"host:i-a03a7758": {
"status": "Alert",
"triggering_value": {
"to_ts": 1475092440,
"value": 2,
"from_ts": 1475092380
},
"last_triggered_ts": 1475092440,
"last_nodata_ts": null,
"name": "host:i-a03a7758",
"last_notified_ts": 1475092440,
"last_resolved_ts": null
}
}
}
}`
output:
{{map[host:i-b3a6cea5:{OK host:i-b3a6cea5} host:i-4d81ca7c:{OK host:i-4d81ca7c} host:i-a03a7758:{Alert host:i-a03a7758}]}}
Your code works with one extra level "state": {} in JSON data: The Go Playground
Your mistake is that Data.State shouldn't be a map. If you change the definition to
type Data struct {
State GroupsData `json:"state"`
}
it works: https://play.golang.org/p/oRjSJMDzU8.