N P
N P

Reputation: 2619

Map response to a struct using Golang

I am attempting to map a response from an API to a struct using Golang.

The JSON that comes back when I view the link in the browser is below:

{
"GBP": 657.54
}

And I just want to map it to a simple struct like so:

type Price struct {
    Name  string
    Value float64
}

Here is my current code.

func FetchCoinPrice(fsym string, tsyms string) Price {
    url := fmt.Sprintf("https://min-api.cryptocompare.com/data/price?fsym=" + fsym + "&tsyms=" + tsyms)

    fmt.Println("Requesting data from " + url)

    price := Price{}

    // getting the data using http
    request, err := http.Get(url)
    if err != nil {
        log.Fatal(err.Error())
    }

    // Read the response body using ioutil
    body, err := ioutil.ReadAll(request.Body)
    if err != nil {
        log.Fatal(err.Error())
    }

    defer request.Body.Close()

    if request.StatusCode == http.StatusOK {
        json.Unmarshal(body, &price)
    }

    return price

}

At the moment all I receive is an empty struct, I know the link is bringing back the correct data and I've tested it in my browser.

Upvotes: 7

Views: 11878

Answers (2)

maerics
maerics

Reputation: 156534

The problem is that the unmarshaler cannot guess that keys in a JSON object should correspond to some value in a struct. Golang JSON mapping simply doesn't work that way.

However, you can make your "Price" type implement json.Unmarshaler to deserialize a message into a map of floats (map[string]float64) then ensure the shape is right and populate the struct accordingly:

func (p *Price) UnmarshalJSON(bs []byte) error {
  kvs := map[string]float64{}
  err := json.Unmarshal(bs, &kvs)
  if err != nil {
    return err
  }
  if len(kvs) != 1 {
    return fmt.Errorf("expected 1 key, got %d", len(kvs))
  }
  for name, value := range kvs {
    p.Name, p.Value = name, value
  }
  return nil
}

func main() {
  jsonstr := `[{"GBP":657.54},{"USD":123.45}]`
  ps := []Price{}
  err := json.Unmarshal([]byte(jsonstr), &ps)
  if err != nil {
    panic(err)
  }
  // ps=[]main.Price{
  //   main.Price{Name:"GBP", Value:657.54},
  //   main.Price{Name:"USD", Value:123.45}
  // }
}

Upvotes: 3

Marc
Marc

Reputation: 21085

The mapping doesn't work that way. Instead, you should use a map:

    data := []byte(`{
       "GBP": 657.54
     }`)

    priceMap := map[string]float64{}
    err := json.Unmarshal(data, &priceMap)
    // Check your errors!
    if err != nil {
      log.Fatal(err.Error())
    }
    fmt.Println(priceMap)

This will print:

map[GBP:657.54]

You can then iterate over the map and build the struct you mentioned above, or just access the entry directly if you know the currency. eg: priceMap["GBP"]

You should really check your errors, especially if you're not getting the output you expect from Unmarshal.

Upvotes: 7

Related Questions