Unmarshaling JSON in golang - json

i'm having a lot of trouble getting my program to work. I want to unmarshal something pretty simple, but it's giving me a lot of issues, unfortunately.
Here is the response that I want to unmarshal:
{"error":[],"result":{"XXBTZUSD":[[1647365820,"39192.0","39192.0","39191.9","39191.9","39191.9","0.18008008",10],[1647365880,"39186.1","39186.1","39172.0","39176.0","39174.4","0.13120077",10]],"last":1647408900}}
I've wrote these structs to help with unmarshalling
type Resp struct {
Error []string `json:"error"`
Result Trades `json:"result"`
}
type Trades struct {
Pair []OHLC `json:"XXBTZUSD"`
Last float64 `json:"last"`
}
type OHLC struct {
Time float64
Open string
High string
Low string
Close string
Vwa string
Volume string
Count float64
}
I have a function call that makes the http request and then unmarshals the data. For whatever reason, my code will end before even starting the function call for the http request and subsequent unmarshalling when the Pair type is []OHLC or []*OHLC. If I change the Pair type to interface{}, then it runs. i want to make it work with the OHLC struct instead though. Below is the complete code:
package main
import (
"fmt"
"net/http"
//"strings"
"io/ioutil"
"encoding/json"
)
type Resp struct {
Error []string `json:"error"`
Result Trades `json:"result"`
}
type Trades struct {
Pair []OHLC `json:"XXBTZUSD"`
Last float64 `json:"last"`
}
type OHLC struct {
TT float64
Open string
High string
Low string
Close string
Vwap string
Volume string
Count float64
}
/*func main() {
var data = [...]Trade{
Trade{5, "op", "hi", "lo", "cl", "vw", "vo", 2},
Trade{5, "op", "hi", "lo", "cl", "vw", "vo", 2},
}
}*/
func main() {
fmt.Println("in main");
getOhlc()
}
func getOhlc() {
fmt.Println("in ohlc func")
resp, err := http.Get("https://api.kraken.com/0/public/OHLC?pair=XXBTZUSD");
if err != nil {
fmt.Errorf("error after request")
return;
}
defer resp.Body.Close();
body, err := ioutil.ReadAll(resp.Body);
if err != nil {
fmt.Errorf("error when reading")
return;
}
var jsonData Resp;
err = json.Unmarshal(body, &jsonData);
if err != nil {
fmt.Errorf("error when unmarshalling")
return
}
if(len(jsonData.Error) > 0) {
fmt.Errorf("error");
return;
}
fmt.Println(jsonData);
}
Any ideas about what might be happening?

"Any ideas about what might be happening?"
The elements in the "XXBTZUSD" JSON array are arrays themselves, i.e. "XXBTZUSD" is an array of arrays. The OHLC type is a struct type. The stdlib will not, by itself, unmarshal a JSON array into a Go struct. Go structs can be used to unmarshal JSON objects. JSON arrays can be unmarshaled into Go slices or arrays.
You would clearly see that that's the issue if you would just print the error from json.Unmarshal:
json: cannot unmarshal array into Go struct field
Trades.result.XXBTZUSD of type main.OHLC
https://go.dev/play/p/D4tjXZVzDI_w
If you want to unmarshal a JSON array into a Go struct you have to have the Go struct type implement a the json.Unmarshaler interface.
func (o *OHLC) UnmarshalJSON(data []byte) error {
// first unmarshal the array into a slice of raw json
raw := []json.RawMessage{}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
// create a function that unmarshals each raw json element into a field
unmarshalFields := func(raw []json.RawMessage, fields ...interface{}) error {
if len(raw) != len(fields) {
return errors.New("bad number of elements in json array")
}
for i := range raw {
if err := json.Unmarshal([]byte(raw[i]), fields[i]); err != nil {
return err
}
}
return nil
}
// call the function
return unmarshalFields(
raw,
&o.Time,
&o.Open,
&o.High,
&o.Low,
&o.Close,
&o.Vwa,
&o.Volume,
&o.Count,
)
}
https://go.dev/play/p/fkFKLkaNaSU

Your code had some issues:
Remove semicolons from end of lines, it's redundant.
fmt.Errorf return error, and not print it, every time check your error and propagate it.
We can convert array of numbers and string to struct in golang.
for achieving your desired output we need to first convert to intermediate container and then convert to our wanted output:
package main
import (
"errors"
"fmt"
"log"
"net/http"
//"strings"
"encoding/json"
"io/ioutil"
)
type Resp struct {
Error []string `json:"error"`
Result Trades `json:"result"`
}
type IntermediateResp struct {
Error []string `json:"error"`
Result IntermediateTrades `json:"result"`
}
type IntermediateTrades struct {
Pair [][]interface{} `json:"XXBTZUSD"`
Last int `json:"last"`
}
type Trades struct {
Pair []OHLC `json:"result"`
Last int `json:"last"`
}
type OHLC struct {
TT float64
Open string
High string
Low string
Close string
Vwap string
Volume string
Count float64
}
/*func main() {
var data = [...]Trade{
Trade{5, "op", "hi", "lo", "cl", "vw", "vo", 2},
Trade{5, "op", "hi", "lo", "cl", "vw", "vo", 2},
}
}*/
func main() {
fmt.Println("in main")
err := getOhlc()
if err != nil {
log.Fatal(err)
}
}
func buildOHLC(l []interface{}) (*OHLC, error) {
if len(l) < 8 {
return nil, errors.New("short list")
}
return &OHLC{
TT: l[0].(float64),
Open: l[1].(string),
High: l[2].(string),
Low: l[3].(string),
Close: l[4].(string),
Vwap: l[5].(string),
Volume: l[6].(string),
Count: l[7].(float64),
}, nil
}
func convert(r IntermediateResp) (*Resp, error) {
result := &Resp{Error: r.Error, Result: Trades{Pair: make([]OHLC, len(r.Result.Pair)), Last: r.Result.Last}}
for i, v := range r.Result.Pair {
ohlc, err := buildOHLC(v)
if err != nil {
return nil, err
}
result.Result.Pair[i] = *ohlc
}
return result, nil
}
func getOhlc() error {
fmt.Println("in ohlc func")
resp, err := http.Get("https://api.kraken.com/0/public/OHLC?pair=XXBTZUSD")
if err != nil {
return fmt.Errorf("error after request, %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
if err != nil {
return fmt.Errorf("error when reading %v", err)
}
var jsonData IntermediateResp
err = json.Unmarshal(body, &jsonData)
if err != nil {
return fmt.Errorf("error when unmarshalling %v", err)
}
if len(jsonData.Error) > 0 {
return fmt.Errorf("error")
}
convertedOhlc, err := convert(jsonData)
if err != nil {
return fmt.Errorf("error when convertedOhlc %v", err)
}
fmt.Println(convertedOhlc)
return nil
}
We define IntermediateResp and IntermediateTrades for Unmarshaling json and then convert it to actual Resp.
I think aother way is using custom Unmarshal for Trades struct.

Related

Is it possible to have a structure for dynamic keys along with static keys for json in Golang

My apologies for the basic question. I am new to Golang and I have the json to parse as below
{
"config1":{
"Parameters":{
"Pm1":"value",
"Pm2":"value",
"Pm3":"value"
},
"dynamic_key1":{
"Parameters":{
"a":"value",
"b":"value",
"c":"value",
"d":"value"
},
"Epoch":"value"
},
"Epoch":"value"
}
}
I am trying to write a struct to parse this json and wrote the struct in the following way.
type Parameters struct {
Pm1 string `json:"Pm1"`
Pm2 string `json:"Pm2"`
Pm3 string `json:"Pm3"`
}
type dynamicParametes struct {
a string `json:"a"`
b string `json:"b"`
c string `json:"c"`
d string `json:"d"`
}
type dynamic struct {
Parameters dynamicParametes `json:"Parameters"`
Epoch string `json:"Epoch"`
}
type config1 struct {
Parameters Parameters `json:"Parameters"`
Dynamic_keys map[string]dynamic `json:"-"`
Epoch string `json:"Epoch"`
}
type config struct {
config1 config1 `json:"config1"`
}
I was hoping that the map will match all the matching keys with dynamic structs and show them in the map. But, I see it created an empty map in the response.
Implemented custom unmarshler for config type.
Note
If you don't need Parameters and dynamicParametes as struct types, you can simply unmarshal them into map[string]string
you have to expose all fields in your structs to do json unmarshaling
validate your json string
type config struct {
Config1 config1 `json:"config1"`
}
type _config config
func (b *config) UnmarshalJSON(data []byte) error {
var v = struct {
Config1 map[string]interface{} `json:"config1"`
}{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
c := _config{}
err := json.Unmarshal(data, &c)
if err != nil {
return err
}
b.Config1.Parameters = c.Config1.Parameters
b.Config1.Epoch = c.Config1.Epoch
if b.Config1.Dynamic_keys == nil {
b.Config1.Dynamic_keys = map[string]dynamic{}
}
for key, config := range v.Config1 {
if key == `Parameters` || key == `Epoch` {
continue
}
data, err := json.Marshal(config)
if err != nil {
return err
}
d := dynamic{}
err = json.Unmarshal(data, &d)
if err != nil {
return err
}
b.Config1.Dynamic_keys[key] = d
}
return nil
}
you can see full code here
All you need is understand how base data types looks in json.
Field Parameters in your json is simple map[string]string and you can unmarshall it with standart json.Unmasrhall without any aditional implementation of interface json.Unmarshaler.
Link for Go Playground with code below
package main
import (
"encoding/json"
"fmt"
)
const jsonStr = `{
"config1":{
"Parameters":{
"Pm1":"value_1",
"Pm2":"value_2",
"Pm3":"value_3"
},
"dynamic_key1":{
"Parameters":{
"a":"value_1",
"b":"value_2",
"c":"value_3",
"d":"value_4"
},
"Epoch":"value"
},
"Epoch":"value"
}
}`
type Data struct {
Config1 struct {
Parameters map[string]string `json:"Parameters"`
Dynamic struct {
Parameters map[string]string `json:"Parameters"`
Epoch string `json:"Epoch"`
} `json:"dynamic_key1"`
Epoch string `json:"Epoch"`
} `json:"config1"`
}
func main() {
var data Data
_ = json.Unmarshal([]byte(jsonStr), &data)
fmt.Printf("%+v\n", data)
}
Output:
{Config1:{Parameters:map[Pm1:value_1 Pm2:value_2 Pm3:value_3] Dynamic:{Parameters:map[a:value_1 b:value_2 c:value_3 d:value_4] Epoch:value} Epoch:value}}

Unmarshalling a single element in an array

Is there a way to unmarshall JSON arrays into single objects in Go?
I have a json response from an endpoint:
{
"results": [
{
"key": "value"
}
]
}
I have a Go struct for the object inside the array:
type Object struct {
Key string `json:"key"`
}
...and a struct for the response object:
type Response struct {
Objects []Object `json:"results"`
}
results is an array of objects, but I know that the endpoint will only ever return an array with 1 object. Is there a way to unmarshall the data and avoid having reference the object by an index? I was hoping I could use something like json:"results[0]" as a field tag.
I'd prefer to be able to:
decoder.Decode(&response)
response.Object.Key
Rather than
decoder.Decode(&response)
response.Objects[0].Key
To does this you need to customize unmarshalling.
An way is create a ResponseCustom like:
//Response json (customized) that match with Unmarshaler interface
type ResponseCustom struct {
Object
}
func (r *ResponseCustom) UnmarshalJSON(b []byte) error{
rsp := &Response{}
err := json.Unmarshal(b, rsp)
if err != nil {
log.Fatalln("error:", err)
} else {
r.Object = rsp.Objects[0]
}
//
return err
}
So you can use ResponseCustom instead of you Response for get Object value.
Look:
func main() {
//
data := []byte(jsondata)
resp := &ResponseCustom{}
//
json.Unmarshal(data, resp)
//
fmt.Println("My Object.value is: " + resp.Object.Key)
}
The result is:
My Object.value is: value
In playground: https://play.golang.org/p/zo7wOSacA4w
Implement unmarshaler interface to convert array of object to object. Fetch the value for key and then unmarshal your json as
package main
import(
"fmt"
"encoding/json"
)
type Object struct {
Key string `json:"key"`
}
func (obj *Object) UnmarshalJSON(data []byte) error {
var v map[string]interface{}
if err := json.Unmarshal(data, &v); err != nil {
fmt.Printf("Error whilde decoding %v\n", err)
return err
}
obj.Key = v["results"].(interface{}).([]interface{})[0].(interface{}).(map[string]interface{})["key"].(interface{}).(string)
return nil
}
func main(){
var obj Object
data := []byte(`{
"results": [
{
"key": "value"
}
]
}`)
if err := json.Unmarshal(data, &obj); err!=nil{
fmt.Println(err)
}
fmt.Printf("%#v", obj)
}
Playground

Best way to handle interfaces in HTTP response

I'm using an API that formats its responses in this way:
{
"err": 0,
"data": **Other json structure**
}
The way I'm getting a response right now is I'm putting the response in an struct like this:
type Response struct {
Err int `json:"err"`
Data interface{} `json:"data"`
}
and then I'm doing this after getting a response
jsonbytes, _ := json.Marshal(resp.Data)
json.Unmarshal(jsonBytes, &dataStruct)
I'm only ignoring errors for this example.
It seems kinda weird to me that I'm marshaling and unmarshaling when I know what the data is supposed to look like and what type it's supposed to be.
Is there a more simple solution that I'm not seeing or is this a normal thing to do?
Edit: I should probably mention that the Data attribute in the response object can vary depending on what API call I'm doing.
The JSON unmarshaller uses reflection to look at the type it is unmarshalling to. Given an uninitialised interface{} as the destination for the unmarshalled data, a JSON object gets unmarshalled into a map[string]interface{} (example in playground).
Here are some ideas.
Option A
If you know the datatype, you can define a new response struct for each type. Example:
type FooResponse struct {
Err int `json:"err"`
Data Foo `json:"data"`
}
type Foo struct {
FooField string `json:"foofield"`
}
type BarResponse struct {
Err int `json:"err"`
Data Bar `json:"data"`
}
type Bar struct {
BarField string `json:"barfield"`
}
Option B
If you prefer to have a single Response struct instead of one per type, you can tell the JSON unmarshaller to avoid unmarshalling the data field until a later time by using the json.RawMessage data type:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Response struct {
Err int `json:"err"`
Data json.RawMessage `json:"data"`
}
type Foo struct {
FooField string `json:"foofield"`
}
type Bar struct {
BarField string `json:"barfield"`
}
func main() {
fooRespJSON := []byte(`{"data":{"foofield":"foo value"}}`)
barRespJSON := []byte(`{"data":{"barfield":"bar value"}}`)
var (
resp Response
foo Foo
bar Bar
)
// Foo
if err := json.Unmarshal(fooRespJSON, &resp); err != nil {
log.Fatal(err)
}
if err := json.Unmarshal(resp.Data, &foo); err != nil {
log.Fatal(err)
}
fmt.Println("foo:", foo)
// Bar
if err := json.Unmarshal(barRespJSON, &resp); err != nil {
log.Fatal(err)
}
if err := json.Unmarshal(resp.Data, &bar); err != nil {
log.Fatal(err)
}
fmt.Println("bar:", bar)
}
Output:
foo: {foo value}
bar: {bar value}
https://play.golang.org/p/Y7D4uhaC4a8
Option C
A third option, as pointed out by #mkopriva in a comment on the question, is to use interface{} as an intermediary datatype and pre-initialise this to a known datatype.
Emphasis lies on the word intermediary -- of course passing around an interface{} is best avoided (Rob Pike's Go Proverbs). The use-case here is to allow any datatype to be used without the need for multiple different Response types. On way to avoid exposing the interface{} is to wrap the response completely, exposing only the data and the error:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Foo struct {
FooField string `json:"foofield"`
}
type Bar struct {
BarField string `json:"barfield"`
}
type Error struct {
Code int
}
func (e *Error) Error() string {
return fmt.Sprintf("error code %d", e.Code)
}
func unmarshalResponse(data []byte, v interface{}) error {
resp := struct {
Err int `json:"err"`
Data interface{} `json:"data"`
}{Data: v}
if err := json.Unmarshal(data, &resp); err != nil {
return err
}
if resp.Err != 0 {
return &Error{Code: resp.Err}
}
return nil
}
func main() {
fooRespJSON := []byte(`{"data":{"foofield":"foo value"}}`)
barRespJSON := []byte(`{"data":{"barfield":"bar value"}}`)
errRespJSON := []byte(`{"err": 123}`)
// Foo
var foo Foo
if err := unmarshalResponse(fooRespJSON, &foo); err != nil {
log.Fatal(err)
}
fmt.Println("foo:", foo)
// Bar
var bar Bar
if err := unmarshalResponse(barRespJSON, &bar); err != nil {
log.Fatal(err)
}
fmt.Println("bar:", bar)
// Error response
var v interface{}
if err := unmarshalResponse(errRespJSON, &v); err != nil {
log.Fatal(err)
}
}
Output:
foo: {foo value}
bar: {bar value}
2009/11/10 23:00:00 error code 123
https://play.golang.org/p/5SVfQGwS-Wy

golang json array unmarshal into type struct

What is causing this to not break apart json? Anyone have an idea on how type is wrong?
With error of:
{"Rates":[{"Symbol":"EURAUD","Bid":"1.45492","Ask":"1.45608","Spread":"11.60","ProductType":"1",},{"Symbol":"Copper","Bid":"2.6068","Ask":"2.6088","Spread":"2.00","ProductType":"3",},{"Symbol":"AUDNZD","Bid":"1.08999","Ask":"1.09177","Spread":"17.80","ProductType":"1",},{"Symbol":"EURSEK","Bid":"9.63786","Ask":"9.65569","Spread":"178.30","ProductType":"1",},{"Symbol":"CADJPY","Bid":"81.629","Ask":"81.708","Spread":"7.90","ProductType":"1",},{"Symbol":"USDCHF","Bid":"0.99463","Ask":"0.99527","Spread":"6.40","ProductType":"1",},{"Symbol":"USDCNH","Bid":"6.8973","Ask":"6.8993","Spread":"20.00","ProductType":"1",},{"Symbol":"US30","Bid":"20950.00","Ask":"20952.00","Spread":"2.00","ProductType":"2",},{"Symbol":"XAGUSD","Bid":"17.202","Ask":"17.25","Spread":"4.80","ProductType":"5",},{"Symbol":"USDSEK","Bid":"8.84794","Ask":"8.85542","Spread":"74.80","ProductType":"1",},{"Symbol":"AUDCHF","Bid":"0.74417","Ask":"0.74588","Spread":"17.10","ProductType":"1",},{"Symbol":"GER30","Bid":"12431.05","Ask":"12433.45","Spread":"2.40","ProductType":"2",},{"Symbol":"USOil","Bid":"49.16","Ask":"49.21","Spread":"5.00","ProductType":"3",},{"Symbol":"GBPNZD","Bid":"1.88546","Ask":"1.88762","Spread":"21.60","ProductType":"1",},{"Symbol":"EURCAD","Bid":"1.48748","Ask":"1.48893","Spread":"14.50","ProductType":"1",},{"Symbol":"EURUSD","Bid":"1.08977","Ask":"1.08997","Spread":"2.00","ProductType":"1",},{"Symbol":"AUS200","Bid":"5922.00","Ask":"5924.00","Spread":"2.00","ProductType":"2",},{"Symbol":"EURJPY","Bid":"121.512","Ask":"121.57","Spread":"5.80","ProductType":"1",},{"Symbol":"EURGBP","Bid":"0.84132","Ask":"0.84208","Spread":"7.60","ProductType":"1",},{"Symbol":"EURNOK","Bid":"9.34136","Ask":"9.36364","Spread":"222.80","ProductType":"1",},{"Symbol":"USDCAD","Bid":"1.36524","Ask":"1.36588","Spread":"6.40","ProductType":"1",},{"Symbol":"GBPCHF","Bid":"1.28753","Ask":"1.28922","Spread":"16.90","ProductType":"1",},{"Symbol":"GBPAUD","Bid":"1.72838","Ask":"1.7303","Spread":"19.20","ProductType":"1",},{"Symbol":"USDJPY","Bid":"111.51","Ask":"111.537","Spread":"2.70","ProductType":"1",},{"Symbol":"USDNOK","Bid":"8.57607","Ask":"8.58684","Spread":"107.70","ProductType":"1",},{"Symbol":"AUDCAD","Bid":"1.02173","Ask":"1.02347","Spread":"17.40","ProductType":"1",},{"Symbol":"FRA40","Bid":"5259.60","Ask":"5267.30","Spread":"7.70","ProductType":"2",},{"Symbol":"AUDUSD","Bid":"0.74858","Ask":"0.74899","Spread":"4.10","ProductType":"1",},{"Symbol":"USDHKD","Bid":"7.77769","Ask":"7.77956","Spread":"18.70","ProductType":"1",},{"Symbol":"NZDCHF","Bid":"0.68192","Ask":"0.68406","Spread":"21.40","ProductType":"1",},{"Symbol":"EURTRY","Bid":"3.86851","Ask":"3.87478","Spread":"62.70","ProductType":"1",},{"Symbol":"AUDJPY","Bid":"83.469","Ask":"83.543","Spread":"7.40","ProductType":"1",},{"Symbol":"USDZAR","Bid":"13.3464","Ask":"13.3941","Spread":"477.00","ProductType":"1",},{"Symbol":"Bund","Bid":"161.78","Ask":"161.81","Spread":"3.00","ProductType":"4",},{"Symbol":"USDMXN","Bid":"18.81249","Ask":"18.83178","Spread":"192.90","ProductType":"1",},{"Symbol":"USDTRY","Bid":"3.54925","Ask":"3.5536","Spread":"43.50","ProductType":"1",},{"Symbol":"USDOLLAR","Bid":"12232.00","Ask":"12237.00","Spread":"5.00","ProductType":"7",},{"Symbol":"JPN225","Bid":"19195.00","Ask":"19205.00","Spread":"10.00","ProductType":"2",},{"Symbol":"UK100","Bid":"7197.80","Ask":"7198.90","Spread":"1.10","ProductType":"2",},{"Symbol":"HKG33","Bid":"24650.00","Ask":"24655.00","Spread":"5.00","ProductType":"2",},{"Symbol":"CADCHF","Bid":"0.72748","Ask":"0.72979","Spread":"23.10","ProductType":"1",},{"Symbol":"NAS100","Bid":"5582.80","Ask":"5583.80","Spread":"1.00","ProductType":"2",},{"Symbol":"NGAS","Bid":"3.2645","Ask":"3.2755","Spread":"11.00","ProductType":"3",},{"Symbol":"ZARJPY","Bid":"8.323","Ask":"8.361","Spread":"3.80","ProductType":"1",},{"Symbol":"GBPCAD","Bid":"1.76724","Ask":"1.76912","Spread":"18.80","ProductType":"1",},{"Symbol":"ESP35","Bid":"10712.00","Ask":"10720.00","Spread":"8.00","ProductType":"2",},{"Symbol":"GBPUSD","Bid":"1.29452","Ask":"1.29527","Spread":"7.50","ProductType":"1",},{"Symbol":"SPX500","Bid":"2384.18","Ask":"2384.68","Spread":"5.00","ProductType":"2",},{"Symbol":"GBPJPY","Bid":"144.336","Ask":"144.448","Spread":"11.20","ProductType":"1",},{"Symbol":"EUSTX50","Bid":"3554.00","Ask":"3555.00","Spread":"1.00","ProductType":"2",},{"Symbol":"TRYJPY","Bid":"31.378","Ask":"31.44","Spread":"6.20","ProductType":"1",},{"Symbol":"NZDCAD","Bid":"0.93642","Ask":"0.93862","Spread":"22.00","ProductType":"1",},{"Symbol":"EURNZD","Bid":"1.58644","Ask":"1.58916","Spread":"27.20","ProductType":"1",},{"Symbol":"XAUUSD","Bid":"1267.79","Ask":"1268.26","Spread":"47.00","ProductType":"5",},{"Symbol":"NZDUSD","Bid":"0.68587","Ask":"0.68692","Spread":"10.50","ProductType":"1",},{"Symbol":"NZDJPY","Bid":"76.489","Ask":"76.607","Spread":"11.80","ProductType":"1",},{"Symbol":"UKOil","Bid":"51.84","Ask":"51.89","Spread":"5.00","ProductType":"3",},{"Symbol":"CHFJPY","Bid":"112.02","Ask":"112.148","Spread":"12.80","ProductType":"1",},{"Symbol":"EURCHF","Bid":"1.08416","Ask":"1.08459","Spread":"4.30","ProductType":"1",}]}
{"Rates":[{"Symbol":"EURAUD","Bid":"1.45492","Ask":"1.45608","Spread":"11.60","ProductType":"1"},{"Symbol":"Copper","Bid":"2.6068","Ask":"2.6088","Spread":"2.00","ProductType":"3"},{"Symbol":"AUDNZD","Bid":"1.08999","Ask":"1.09177","Spread":"17.80","ProductType":"1"},{"Symbol":"EURSEK","Bid":"9.63786","Ask":"9.65569","Spread":"178.30","ProductType":"1"},{"Symbol":"CADJPY","Bid":"81.629","Ask":"81.708","Spread":"7.90","ProductType":"1"},{"Symbol":"USDCHF","Bid":"0.99463","Ask":"0.99527","Spread":"6.40","ProductType":"1"},{"Symbol":"USDCNH","Bid":"6.8973","Ask":"6.8993","Spread":"20.00","ProductType":"1"},{"Symbol":"US30","Bid":"20950.00","Ask":"20952.00","Spread":"2.00","ProductType":"2"},{"Symbol":"XAGUSD","Bid":"17.202","Ask":"17.25","Spread":"4.80","ProductType":"5"},{"Symbol":"USDSEK","Bid":"8.84794","Ask":"8.85542","Spread":"74.80","ProductType":"1"},{"Symbol":"AUDCHF","Bid":"0.74417","Ask":"0.74588","Spread":"17.10","ProductType":"1"},{"Symbol":"GER30","Bid":"12431.05","Ask":"12433.45","Spread":"2.40","ProductType":"2"},{"Symbol":"USOil","Bid":"49.16","Ask":"49.21","Spread":"5.00","ProductType":"3"},{"Symbol":"GBPNZD","Bid":"1.88546","Ask":"1.88762","Spread":"21.60","ProductType":"1"},{"Symbol":"EURCAD","Bid":"1.48748","Ask":"1.48893","Spread":"14.50","ProductType":"1"},{"Symbol":"EURUSD","Bid":"1.08977","Ask":"1.08997","Spread":"2.00","ProductType":"1"},{"Symbol":"AUS200","Bid":"5922.00","Ask":"5924.00","Spread":"2.00","ProductType":"2"},{"Symbol":"EURJPY","Bid":"121.512","Ask":"121.57","Spread":"5.80","ProductType":"1"},{"Symbol":"EURGBP","Bid":"0.84132","Ask":"0.84208","Spread":"7.60","ProductType":"1"},{"Symbol":"EURNOK","Bid":"9.34136","Ask":"9.36364","Spread":"222.80","ProductType":"1"},{"Symbol":"USDCAD","Bid":"1.36524","Ask":"1.36588","Spread":"6.40","ProductType":"1"},{"Symbol":"GBPCHF","Bid":"1.28753","Ask":"1.28922","Spread":"16.90","ProductType":"1"},{"Symbol":"GBPAUD","Bid":"1.72838","Ask":"1.7303","Spread":"19.20","ProductType":"1"},{"Symbol":"USDJPY","Bid":"111.51","Ask":"111.537","Spread":"2.70","ProductType":"1"},{"Symbol":"USDNOK","Bid":"8.57607","Ask":"8.58684","Spread":"107.70","ProductType":"1"},{"Symbol":"AUDCAD","Bid":"1.02173","Ask":"1.02347","Spread":"17.40","ProductType":"1"},{"Symbol":"FRA40","Bid":"5259.60","Ask":"5267.30","Spread":"7.70","ProductType":"2"},{"Symbol":"AUDUSD","Bid":"0.74858","Ask":"0.74899","Spread":"4.10","ProductType":"1"},{"Symbol":"USDHKD","Bid":"7.77769","Ask":"7.77956","Spread":"18.70","ProductType":"1"},{"Symbol":"NZDCHF","Bid":"0.68192","Ask":"0.68406","Spread":"21.40","ProductType":"1"},{"Symbol":"EURTRY","Bid":"3.86851","Ask":"3.87478","Spread":"62.70","ProductType":"1"},{"Symbol":"AUDJPY","Bid":"83.469","Ask":"83.543","Spread":"7.40","ProductType":"1"},{"Symbol":"USDZAR","Bid":"13.3464","Ask":"13.3941","Spread":"477.00","ProductType":"1"},{"Symbol":"Bund","Bid":"161.78","Ask":"161.81","Spread":"3.00","ProductType":"4"},{"Symbol":"USDMXN","Bid":"18.81249","Ask":"18.83178","Spread":"192.90","ProductType":"1"},{"Symbol":"USDTRY","Bid":"3.54925","Ask":"3.5536","Spread":"43.50","ProductType":"1"},{"Symbol":"USDOLLAR","Bid":"12232.00","Ask":"12237.00","Spread":"5.00","ProductType":"7"},{"Symbol":"JPN225","Bid":"19195.00","Ask":"19205.00","Spread":"10.00","ProductType":"2"},{"Symbol":"UK100","Bid":"7197.80","Ask":"7198.90","Spread":"1.10","ProductType":"2"},{"Symbol":"HKG33","Bid":"24650.00","Ask":"24655.00","Spread":"5.00","ProductType":"2"},{"Symbol":"CADCHF","Bid":"0.72748","Ask":"0.72979","Spread":"23.10","ProductType":"1"},{"Symbol":"NAS100","Bid":"5582.80","Ask":"5583.80","Spread":"1.00","ProductType":"2"},{"Symbol":"NGAS","Bid":"3.2645","Ask":"3.2755","Spread":"11.00","ProductType":"3"},{"Symbol":"ZARJPY","Bid":"8.323","Ask":"8.361","Spread":"3.80","ProductType":"1"},{"Symbol":"GBPCAD","Bid":"1.76724","Ask":"1.76912","Spread":"18.80","ProductType":"1"},{"Symbol":"ESP35","Bid":"10712.00","Ask":"10720.00","Spread":"8.00","ProductType":"2"},{"Symbol":"GBPUSD","Bid":"1.29452","Ask":"1.29527","Spread":"7.50","ProductType":"1"},{"Symbol":"SPX500","Bid":"2384.18","Ask":"2384.68","Spread":"5.00","ProductType":"2"},{"Symbol":"GBPJPY","Bid":"144.336","Ask":"144.448","Spread":"11.20","ProductType":"1"},{"Symbol":"EUSTX50","Bid":"3554.00","Ask":"3555.00","Spread":"1.00","ProductType":"2"},{"Symbol":"TRYJPY","Bid":"31.378","Ask":"31.44","Spread":"6.20","ProductType":"1"},{"Symbol":"NZDCAD","Bid":"0.93642","Ask":"0.93862","Spread":"22.00","ProductType":"1"},{"Symbol":"EURNZD","Bid":"1.58644","Ask":"1.58916","Spread":"27.20","ProductType":"1"},{"Symbol":"XAUUSD","Bid":"1267.79","Ask":"1268.26","Spread":"47.00","ProductType":"5"},{"Symbol":"NZDUSD","Bid":"0.68587","Ask":"0.68692","Spread":"10.50","ProductType":"1"},{"Symbol":"NZDJPY","Bid":"76.489","Ask":"76.607","Spread":"11.80","ProductType":"1"},{"Symbol":"UKOil","Bid":"51.84","Ask":"51.89","Spread":"5.00","ProductType":"3"},{"Symbol":"CHFJPY","Bid":"112.02","Ask":"112.148","Spread":"12.80","ProductType":"1"},{"Symbol":"EURCHF","Bid":"1.08416","Ask":"1.08459","Spread":"4.30","ProductType":"1"}]}
panic: json: cannot unmarshal string into Go value of type
main.MsgRatesArray
goroutine 1 [running]:
main.main()
/tmp/test.go:50 +0x52c
With this code:
package main
import (
"log"
"fmt"
"net/http"
"bytes"
"io/ioutil"
"strings"
"github.com/pquerna/ffjson/ffjson"
)
type MsgRatesArray struct {
RateQuote []MsgRateQuoteJson `json:"Rates"`
}
type MsgRateQuoteJson struct {
SymbolName string `json:"Symbol"`
SymbolBid int64 `json:"Bid"`
SymbolAsk int64 `json:"Ask"`
SymbolSpread int64 `json:"Spread"`
SymbolPT string `json:"ProductType"`
}
var respBytes []byte
func main() {
var msg MsgRatesArray
response,err := http.Get("https://ratesjson.fxcm.com/DataDisplayer")
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
respBytes, err := ioutil.ReadAll(response.Body)
//Get bad JSON into string
jsonBytes := respBytes[bytes.Index(respBytes, []byte("{")):bytes.LastIndex(respBytes, []byte("}"))+1]
jsonString := string(jsonBytes)
fmt.Println(jsonString)
// Use a positive number to indicate max replacement count to fix bad JSON string so we can remove comma in JSON ARRAY.
result := strings.Replace(jsonString, "\",}", "\"}", -1)
fmt.Println(result)
// Turn GOOD JSON string back to JSON BYTES (BIN)
jsonBytes2, err := ffjson.Marshal(result)
if err != nil {
panic(err)
}
// Parse JSON !
err = ffjson.Unmarshal(jsonBytes2, &msg)
if err != nil {
panic(err)
}
}
What is wrong with type for json array?
In the returned json Bid, Ask, and Spread are json strings not integers, so change your type definition to this:
type MsgRateQuoteJson struct {
SymbolName string `json:"Symbol"`
SymbolBid string `json:"Bid"`
SymbolAsk string `json:"Ask"`
SymbolSpread string `json:"Spread"`
SymbolPT string `json:"ProductType"`
}
And marshaling a json string to get json bytes is not the correct way, just convert the string to a byte slice like this:
jsonBytes2 := []byte(result)
... and you're good to go:
// Parse JSON !
err = ffjson.Unmarshal(jsonBytes2, &msg)
if err != nil {
panic(err)
}
Edit:
If you want to convert those strings into specific types during the json unmarshaling, you can do so by defining an UnmarshalJSON method on the *MsgRateQuoteJson type, plus with the help of the strconv package, like this:
type MsgRateQuoteJson struct {
SymbolName string `json:"Symbol"`
SymbolBid float64 `json:"Bid"`
SymbolAsk float64 `json:"Ask"`
SymbolSpread float64 `json:"Spread"`
SymbolPT int64 `json:"ProductType"`
}
func (msg *MsgRateQuoteJson) UnmarshalJSON(data []byte) (err error) {
m := map[string]string{}
if err = ffjson.Unmarshal(data, &m); err != nil {
return err
}
msg.SymbolName = m["Symbol"]
if msg.SymbolBid, err = strconv.ParseFloat(m["Bid"], 64); err != nil {
return err
}
if msg.SymbolAsk, err = strconv.ParseFloat(m["Ask"], 64); err != nil {
return err
}
if msg.SymbolSpread, err = strconv.ParseFloat(m["Spread"], 64); err != nil {
return err
}
if msg.SymbolPT, err = strconv.ParseInt(m["ProductType"], 10, 64); err != nil {
return err
}
return nil
}

Golang check if interface type is nil

When unmarshalling a string of json, using Golang's json.Unmarshal() function, I always unmarshal the string into a map[string]interface{} - I'm not sure weather there is a more optimal way of decoding json.
To get to the point:
Sometimes the json's unmarshalled type is nil, not string (or int etc.). This always throws a panic, saying:
interface conversion: interface is nil, not int
How can I avoid this panic or check if the interface's type is nil or not?
Example:
Here is an example of my problem in action: https://play.golang.org/p/0ATzXBbdoS
Check if the key exist instead of letting it panic.
func keyExists(decoded map[string]interface{}, key string) {
val, ok := decoded[key]
return ok && val != nil
}
func main() {
jsonText := `{
"name": "Jimmy",
"age": 23
}`
var decoded map[string]interface{}
if err := json.Unmarshal([]byte(jsonText), &decoded); err != nil {
fmt.Println(err)
os.Exit(0)
}
if keyExists(decoded, "name") {
fmt.Println(decoded["name"].(string))
}
if keyExists(decoded, "age") {
fmt.Println(decoded["age"].(float64))
}
if keyExists(decoded, "gender") {
fmt.Println(decoded["gender"].(int))
}
}
Also, this is far from being optimal if you know what your json will look like. As specified in the documentation, you can unmarshal it directly into a struct:
type Human struct {
Name string
Age int
Gender int
}
func main() {
jsonText := `{
"name": "Jimmy",
"age": 23
}`
decoded := Human{}
if err := json.Unmarshal([]byte(jsonText), &decoded); err != nil {
fmt.Println(err)
os.Exit(0)
}
fmt.Println(decoded.Name)
fmt.Println(decoded.Age)
fmt.Println(decoded.Gender)
}