无法从 HTTP 响应中获取 JSON 解析

Can't Get JSON Parsed from HTTP Response

所以,我试图弄清楚如何获取以下代码以正确解析来自 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 的 JSON 数据,但仅在指向时获取 empty/zero 值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)
}

我怀疑这与 CoinMarketCap 的 JSON 位于顶级 JSON 数组中这一事实有关,但我尝试了各种迭代,例如:

priceData := make([]CoinMarketCapData, 1)

无济于事。非常感谢任何建议,谢谢。

JSON是一个数组,所以需要传递一个数组给Decode方法。还要记得检查返回的错误。

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)
  }
}

运行 这会打印出

2016/08/21 17:15:27 Ethereum Id: ethereum

你说得对,顶级 API 响应类型是一个列表,必须在解组过程中反映出来。解决此问题的一种方法是将您的 MarketCap 响应定义为切片,如下所示:

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"`
}

然后解组就可以正常工作了。需要注意的一件事是指向切片的指针与切片不同。特别是,指针不支持索引,这就是为什么需要先取消引用它才能访问列表中的第一项。

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
}

另一种方法是为单个 MarketCap 定义一个类型,然后在需要时创建一个切片作为目标:

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
}

哪种更适合您取决于您​​的用例。如果您希望结构反映 API 响应,那么第一种方法似乎更合适。 MarketCap 是一回事吗,我相信 API 只是访问它的一种方式,比第二种更合适。