Search code examples
jsongostructunmarshalling

Unmarshaling JSON in golang


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?


Solution

  • "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