So, I'm trying to figure out how to get the following code to properly parse the JSON data from https://api.coinmarketcap.com/v1/ticker/ethereum. It seems to have no problem decoding the JSON data in the response from http://echo.jsontest.com/key1/value1/key2/value2, but only gets empty/zero values when pointed at the CoinMarketCap API.
package main
import(
"encoding/json"
"net/http"
"log"
)
type JsonTest struct {
Key1 string
Key2 string
}
type CoinMarketCapData struct {
Id string
Name string
Symbol string
Rank int
PriceUSD float64
PriceBTC float64
Volume24hUSD float64
MarketCapUSD float64
AvailableSupply float64
TotalSupply float64
PercentChange1h float32
PercentChange24h float32
PercentChange7d float32
}
func getJson(url string, target interface{}) error {
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Content-Type", "application/json")
r, err := client.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(target)
}
func main() {
//Test with dummy JSON
url1 := "http://echo.jsontest.com/key1/value1/key2/value2"
jsonTest := new(JsonTest)
getJson(url1, jsonTest)
log.Printf("jsonTest Key1: %s", jsonTest.Key1)
//Test with CoinMarketCap JSON
url2 := "https://api.coinmarketcap.com/v1/ticker/ethereum"
priceData := new(CoinMarketCapData)
getJson(url2, priceData)
//Should print "Ethereum Id: ethereum"
log.Printf("Ethereum Id: %s", priceData.Id)
}
I suspect it's related to the fact that the JSON at CoinMarketCap is inside a top level JSON array, but I've tried various iterations of things like:
priceData := make([]CoinMarketCapData, 1)
to no avail. Any advice is much appreciated, thanks.
The JSON is an array, so you need to pass an array to the Decode method. Also remember to check the returned error.
package main
import(
"encoding/json"
"net/http"
"log"
)
type CoinMarketCapData struct {
Id string
Name string
Symbol string
Rank int
PriceUSD float64
PriceBTC float64
Volume24hUSD float64
MarketCapUSD float64
AvailableSupply float64
TotalSupply float64
PercentChange1h float32
PercentChange24h float32
PercentChange7d float32
}
func getJson(url string, target interface{}) error {
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Content-Type", "application/json")
r, err := client.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(target)
}
func main() {
//Test with CoinMarketCap JSON
url2 := "https://api.coinmarketcap.com/v1/ticker/ethereum"
priceData := make([]CoinMarketCapData, 0)
err := getJson(url2, &priceData)
if err != nil {
log.Printf("Failed to decode json: %v", err)
} else {
//Should print "Ethereum Id: ethereum"
log.Printf("Ethereum Id: %v", priceData[0].Id)
}
}
running this prints
2016/08/21 17:15:27 Ethereum Id: ethereum
You're right, the top level API response type is a list and that has to be reflected in the unmarshalling process. One way to fix this would be to define your MarketCap response as slice, like this:
package main
import (
"encoding/json"
"log"
"net/http"
)
// curl -sLf "https://api.coinmarketcap.com/v1/ticker/ethereum" | JSONGen
// command line helper: `go get github.com/bemasher/JSONGen`
type MarketCapResponse []struct {
AvailableSupply float64 `json:"available_supply"`
HVolumeUsd float64 `json:"24h_volume_usd"`
Id string `json:"id"`
MarketCapUsd float64 `json:"market_cap_usd"`
Name string `json:"name"`
PercentChange1h float64 `json:"percent_change_1h"`
PercentChange24h float64 `json:"percent_change_24h"`
PercentChange7d float64 `json:"percent_change_7d"`
PriceBtc float64 `json:"price_btc"`
PriceUsd float64 `json:"price_usd"`
Rank int64 `json:"rank"`
Symbol string `json:"symbol"`
TotalSupply float64 `json:"total_supply"`
}
Then the unmarshaling would work just fine. One thing to note is that pointer to a slice is different from the slice. Especially, the pointer does not support indexing, which is why need to dereference it first to access the first item in the list.
func getAndUnmarshal(url string, target interface{}) error {
var client = &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Content-Type", "application/json")
r, _ := client.Do(req)
defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(target)
}
func main() {
link := "https://api.coinmarketcap.com/v1/ticker/ethereum"
resp := new(MarketCapResponse)
getAndUnmarshal(link, resp)
log.Printf("Ethereum Id: %s", (*resp)[0].Id)
// prints:
// 2016/08/22 02:13:23 Ethereum Id: ethereum
}
Another way would be to define a type for a single MarketCap and then create a slice as a target when needed:
package main
// curl -sLf "https://api.coinmarketcap.com/v1/ticker/ethereum" | jq .[0] | JSONGen
type MarketCap struct {
AvailableSupply float64 `json:"available_supply"`
HVolumeUsd float64 `json:"24h_volume_usd"`
Id string `json:"id"`
MarketCapUsd float64 `json:"market_cap_usd"`
Name string `json:"name"`
PercentChange1h float64 `json:"percent_change_1h"`
PercentChange24h float64 `json:"percent_change_24h"`
PercentChange7d float64 `json:"percent_change_7d"`
PriceBtc float64 `json:"price_btc"`
PriceUsd float64 `json:"price_usd"`
Rank int64 `json:"rank"`
Symbol string `json:"symbol"`
TotalSupply float64 `json:"total_supply"`
}
func getAndUnmarshal(url string, target interface{}) error {
...
}
func main() {
link := "https://api.coinmarketcap.com/v1/ticker/ethereum"
resp := make([]MarketCap, 0)
getAndUnmarshal(link, &resp)
log.Printf("Ethereum Id: %s", resp[0].Id)
// 2016/08/22 02:13:23 Ethereum Id: ethereum
}
What is more suitable for you will depend on your use case. If you want the struct to reflect the API response, then the first approach seems more suitable. Is MarketCap a thing and the API is just one way to access it, than the second fits better, I believe.