在GO语言中使用API​​时如何将JSON转换为Go类型定义

I am building a an application that consumes an API then also saves the json data to go lang structs then later I will make end points that will provide results for certain calculations. I have implemented consuming the API the challenging part is how to save the data in a way that go understands. Which is a proper approach?

The following is the JSON format when I make a request. The key I am interested in is only Time Series (1min)

JSON

{
    "Meta Data": {
        "1. Information": "Intraday (1min) prices and volumes",
        "2. Symbol": "MSFT",
        "3. Last Refreshed": "2018-05-24 16:00:00",
        "4. Interval": "1min",
        "5. Output Size": "Compact",
        "6. Time Zone": "US/Eastern"
    },
    "Time Series (1min)": {
        "2018-05-24 16:00:00": {
            "1. open": "98.3050",
            "2. high": "98.3600",
            "3. low": "98.2500",
            "4. close": "98.3100",
            "5. volume": "2377114"
        },
        "2018-05-24 15:59:00": {
            "1. open": "98.2900",
            "2. high": "98.3300",
            "3. low": "98.2900",
            "4. close": "98.3000",
            "5. volume": "137133"
        },
        "2018-05-24 15:58:00": {
            "1. open": "98.2900",
            "2. high": "98.3000",
            "3. low": "98.2600",
            "4. close": "98.2900",
            "5. volume": "135875"
        },
        "2018-05-24 15:53:00": {
            "1. open": "98.2750",
            "2. high": "98.2950",
            "3. low": "98.2600",
            "4. close": "98.2700",
            "5. volume": "77959"
        }
    }
}

code

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "github.com/jmoiron/jsonq"
)

func main() {
    response, err := http.Get("https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=MSFT&interval=1min&apikey=demo")
    if err != nil {
        fmt.Printf("The HTTP request failed with error %s
", err)
    } else {
        data, _ := ioutil.ReadAll(response.Body)
        // fmt.Println(string(data))
    }
}

Unfortunately, this data is poorly structured for easy unmarshaling via the golang JSON infrastructure.

The general approach for unmarshaling data such as this example is to define a type (or set of types) which contain the structure you desire and implement the json.Unmarshaler interface with logic to inspect the incoming structure and populate the desired structs manually.

For example:

type Quote struct {
  Time                   string
  Open, High, Low, Close float32
  Volume                 int
}

type Quotes []Quote

func main() {
  qs := Quotes{}
  err := json.Unmarshal([]byte(jsonstr), &qs)
  if err != nil {
    panic(err)
  }
  fmt.Printf("%#v
", qs)
}

const targetName = "Time Series (1min)"

func (qs *Quotes) UnmarshalJSON(bs []byte) error {
  // Unmarshal into a generic map
  obj := make(map[string]interface{})
  err := json.Unmarshal(bs, &obj)
  if err != nil {
    return err
  }

  // Find the target time series
  entries, ok := obj[targetName].(map[string]interface{})
  if !ok {
    return fmt.Errorf("cannot find entry with name %q", targetName)
  }

  // Parse a Quote object from each entry in the target object
  quotes := []Quote{}
  for timestamp, values := range entries {
    values, ok := values.(map[string]interface{})
    if !ok {
      return fmt.Errorf("value for %q is not an object", timestamp)
    }
    quote := Quote{}
    quote.Time = timestamp
    v, err := strconv.ParseFloat(values["1. open"].(string), 32)
    if err != nil {
      return err
    }
    quote.Open = float32(v)
    // Repeat for each of Close,High,Low,Volume...
    quotes = append(quotes, quote)
  }

  *qs = Quotes(quotes)
  return nil
}