jsongostructunmarshallingkraken.com

Unmarshal a nested json without a key


I am not unable to cast the following json in a struct in Golang, received from Kraken API:

{
    "error": [],
    "result": {
        "LINKUSD": {
            "asks": [
                ["2.049720", "183.556", 1576323009],
                ["2.049750", "555.125", 1576323009],
                ["2.049760", "393.580", 1576323008],
                ["2.049980", "206.514", 1576322995]
            ],
            "bids": [
                ["2.043800", "20.691", 1576322350],
                ["2.039080", "755.396", 1576323007],
                ["2.036960", "214.621", 1576323006],
                ["2.036930", "700.792", 1576322987]
            ]
        }
    }
}

Using json-to-go, he gives me the following struct:

type AutoGenerated struct {
    Error  []interface{} `json:"error"`
    Result struct {
        LINKUSD struct {
            Asks [][]interface{} `json:"asks"`
            Bids [][]interface{} `json:"bids"`
        } `json:"LINKUSD"`
    } `json:"result"`
}

Obviously, i can't hardocode the LINKUSD cause it will change for every currency pairs.

I've created two structure for accomplish the task, but i'm not able to cast the result in the struct.

type BitfinexOrderBook struct {
    Pair string          `json:"pair"`
    Asks []BitfinexOrder `json:"asks"`
    Bids []BitfinexOrder `json:"bids"`
}

type BitfinexOrder struct {
    Price     string
    Volume    string
    Timestamp time.Time
}

My first attempt was to use reflection. Reading the JSON data posted above, i'm able to retrieve an interface that contains the asks and bids list.

// Just used as a divisor
const div string = " ---------------------------------"
func test(data []byte) error {
    var err error

    // Creating the maps for the JSON data
    m := map[string]interface{}{}

    // Parsing/Unmarshalling the json readed from the file
    err = json.Unmarshal(data, &m)

    if err != nil {
        log.Println("Error unmarshalling data: " + err.Error())
        return err
    }

    // Extract the "result" only
    a := reflect.ValueOf(m["result"])

    if a.Kind() == reflect.Map {
        key := a.MapKeys()[0] // Extract the key -> LINKUSD
        log.Println("KEY: ", key, div)
        strct := a.MapIndex(key) // Extract the value -> asks and bids array
        log.Println("MAP: ", strct, div)
        m, _ := strct.Interface().(map[string]interface{})
        log.Println("Asks: ", m["asks"], div) // This will print [[value, value, value] ...] related to asks
        log.Println("Bids: ", m["bids"], div) // This will print [[value, value, value] ...] related to bids

        // Parse the interface into a []byte
        asks_data, err := json.Marshal(m["asks"])
        log.Println("OK: ", err, div)
        log.Println("ASKS: ", string(asks_data), div)
        // Tried without array to (datastructure.BitfinexOrder)
        var asks []datastructure.BitfinexOrder
        err = json.Unmarshal(asks_data, &asks)
        log.Println("ERROR: ", err, div)
        log.Println("UNMARSHAL: ", asks, div)

    }
    return errors.New("UNABLE_PARSE_VALUE")
}

The two print below m, _ := strct.Interface().(map[string]interface{}) will show the following similar data that i'm not able to cast due to the fact that are of interface type:

[[2.049720 183.556 1.576323009e+09] [2.049750 555.125 1.576323009e+09] [2.049760 393.580 1.576323008e+09] [2.049980 206.514 1.576322995e+09]]

But i'm not able to unmarshal the data.

So i've tried with a different function provided by @chmike:

// UnmarshalJSON decode a BifinexOrder.
func UnmarshalJSON(data []byte) (datastructure.BitfinexOrder, error) {
    var packedData []json.Number

    var order datastructure.BitfinexOrder
    err := json.Unmarshal(data, &packedData)
    if err != nil {
        return order, err
    }
    order.Price = packedData[0].String()
    order.Volume = packedData[1].String()
    t, err := packedData[2].Int64()
    if err != nil {
        return order, err
    }
    order.Timestamp = time.Unix(t, 0)
    return order, nil
}

But i receive the following error:

json: cannot unmarshal array into Go value of type json.Number

Some tips?

NOTE: My previous questions was closed as a duplicate of "Unmarshal 2 different structs in a slice". But, if you read the questions, i'm not dealing with two different structs... I'm dealing with a json that contains a key that i does not know at prior. I'm not able to marshal BitfinexOrder too


Solution

  • Solution found from @mkopriva and @chmike:

    Thank you guys!

    package main
    
    import (
        "fmt"
        "time"
        "encoding/json"
    )
    
    var data = []byte(`{
        "error": [],
        "result": {
            "LINKUSD": {
                "asks": [
                    ["2.049720", "183.556", 1576323009],
                    ["2.049750", "555.125", 1576323009],
                    ["2.049760", "393.580", 1576323008],
                    ["2.049980", "206.514", 1576322995]
                ],
                "bids": [
                    ["2.043800", "20.691", 1576322350],
                    ["2.039080", "755.396", 1576323007],
                    ["2.036960", "214.621", 1576323006],
                    ["2.036930", "700.792", 1576322987]
                ]
            }
        }
    }`)
    
    type Response struct {
        Error  []interface{}          `json:"error"`
        Result map[string]Order `json:"result"`
    }
    
    type Order struct {
        Asks []BitfinexOrder `json:"asks"`
        Bids []BitfinexOrder `json:"bids"`
    }
    
    type BitfinexOrder struct {
        Price     string
        Volume    string
        Timestamp time.Time
    }
    
    // UnmarshalJSON decode a BifinexOrder.
    func (b *BitfinexOrder) UnmarshalJSON(data []byte) error {
        var packedData []json.Number
        err := json.Unmarshal(data, &packedData)
        if err != nil {
            return err
        }
        b.Price = packedData[0].String()
        b.Volume = packedData[1].String()
        t, err := packedData[2].Int64()
        if err != nil {
            return err
        }
        b.Timestamp = time.Unix(t, 0)
        return nil
    }
    
    func main() {
        res := &Response{}
        if err := json.Unmarshal(data, res); err != nil {
            panic(err)
        }
    
        for key, value := range res.Result {
            fmt.Println(key)
            for i, ask := range value.Asks {
                fmt.Printf("Asks[%d] = %#v\n", i, ask)
            }
            for i, bid := range value.Bids {
                fmt.Printf("Bids[%d] = %#v\n", i, bid)
            }
        }
    }