-2

I have a JSON coming from the Kraken websocket that looks like this:

myJsonString := []byte(`[320,{"as":[["27346.80000","0.48700000","1609084588.716887"],["27350.00000","1.38867881","1609084599.606134"]],"bs":[["27346.70000","0.39439437","1609084613.201520"],["27342.10000","0.08750000","1609084611.036191"]]},"book-10","XBT/USD"]`)

And I'd like to unmarshal to a struct like this:

type OrderBook struct {
    ChannelID   int
    A []OrderBookItem     `json:"as"`
    B []OrderBookItem     `json:"bs"`
    Depth       string
    Pair        string
}

type OrderBookItem struct {
    Price       float64
    Volume      float64
    Time        float64
}

I tried with unmarshal into a slice, but it doesn't give the desired result as it just returns a struct with zero's. I read this answer How to parse JSON arrays with two different data types into a struct in Golang, but there the message does not seem to be an array.

func main() {

    myJsonString := []byte(`[320,{"as":[["27346.80000","0.48700000","1609084588.716887"],["27350.00000","1.38867881","1609084599.606134"]],"bs":[["27346.70000","0.39439437","1609084613.201520"],["27342.10000","0.08750000","1609084611.036191"]]},"book-10","XBT/USD"]`)

    var raw []OrderBook
    json.Unmarshal(myJsonString, &raw)
    fmt.Println(raw)
}

Result:

[{0 [] []  } {0 [{0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0}] [{0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0} {0 0 0}]  } {0 [] []  } {0 [] []  }]

I also tried unmarshaling with interface{} and using switch to access the values, but that seems very cumbersome.

What would be the best practice to unmarshal this into a struct?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Hans
  • 7
  • 4
    Paste your json [here](https://app.quicktype.io), and select your language on the right. It will give you the correct model and parsing code. – koen Jan 03 '21 at 13:50
  • The json is json in layout only (Certainly not in data description). You could also just state it is an array of strings with positional indications of what the data is: Index 0: Int, index 1: a json, index 2: string, etc. The index must have a meaning to be able to distinguish between the fields, so a way to parse it could just to use a split of the string and cast the data by index thus avoiding the switch type statements all together. – Norbert Jan 03 '21 at 14:54
  • Thank you! Could you elaborate on how to approach the casting by index? When I access myJsonString[i] it seems to give back bytes. If I convert to string, it gives back each individual character. When I use split on "," it also splits the json at 'index 1' – Hans Jan 03 '21 at 15:39
  • You can’t Unmarshal an array into a struct by default. If you don’t want to do the decoding yourself, just use another slice. – JimB Jan 03 '21 at 15:52

1 Answers1

1

You can implement a custom UnmarshalJSON method for your type.

func (ob *OrderBook) UnmarshalJSON(b []byte) error {
    var values []interface{}
    err := json.Unmarshal(b, &values)
    if err != nil {
        return err
    }

    if len(values) != 4 {
        return fmt.Errorf("unexpected number of order book values: %d", len(values))
    }

    ob.ChannelID = int(values[0].(float64))
    ob.Depth = values[2].(string)
    ob.Pair = values[3].(string)

    m := values[1].(map[string]interface{})

    ob.A, err = parseOrderBookItems(m["as"].([]interface{}))
    if err != nil {
        return err
    }

    ob.B, err = parseOrderBookItems(m["bs"].([]interface{}))
    return err
}

func parseOrderBookItems(items []interface{}) ([]OrderBookItem, error) {
    bookItems := make([]OrderBookItem, len(items))

    for i, item := range items {
        values := item.([]interface{})
        if len(values) != 3 {
            return nil, fmt.Errorf("unexpected number of order book item values: %d", len(values))
        }

        obi := OrderBookItem{}
        f, err := strconv.ParseFloat(values[0].(string), 64)
        if err != nil {
            return nil, err
        }
        obi.Price = f

        f, err = strconv.ParseFloat(values[1].(string), 64)
        if err != nil {
            return nil, err
        }
        obi.Volume = f

        f, err = strconv.ParseFloat(values[2].(string), 64)
        if err != nil {
            return nil, err
        }
        obi.Time = f

        bookItems[i] = obi
    }

    return bookItems, nil
}

Here's an example on the playground: https://play.golang.org/p/j7ydQCiqpcS.

Gavin
  • 4,365
  • 1
  • 18
  • 27