I'm new to Go and can't figure out how to simply read and average the values of an array of JSONs. I also want to round my result to 1 decimal point, but Go has no Round()
function. Here's the data:
[
{"millisUTC":"1496424000000","price":"7.6"},
{"millisUTC":"1496423700000","price":"7.5"},
{"millisUTC":"1496423400000","price":"9.1"},
{"millisUTC":"1496423100000","price":"9.2"},
{"millisUTC":"1496422800000","price":"10.0"}
]
I want to get the prices and average them, rounding to 1 decimal point. Yet it took me over 30 lines of code, when (as a Ruby developer) it would usually take me 3 lines. How do I simplify this? My code takes in 2 parameters, starttime
and endtime
, and calls an API: https://github.com/rayning0/griddy/blob/master/controllers/default.go
type arrayOfMaps []map[string]string
func getAvgPrice(starttime, endtime string) float64 {
response, err := http.Get("https://hourlypricing.comed.com/api?type=5minutefeed&datestart=" + starttime + "&dateend=" + endtime)
if err != nil {
fmt.Println(err)
}
defer response.Body.Close()
energyJSON, err := ioutil.ReadAll(response.Body)
if err != nil {
fmt.Println(err)
}
var energyPrices arrayOfMaps
err = json.Unmarshal(energyJSON, &energyPrices)
fmt.Println("Energy prices between", starttime, "and", endtime)
fmt.Println(energyPrices)
var sum float64
var size int
for _, p := range energyPrices {
price, _ := strconv.ParseFloat(p["price"], 64)
sum += price
size++
}
avg := Truncate(sum / float64(size))
fmt.Println("Average price:", avg)
return avg
}
//Truncate a float to 1 level of precision
func Truncate(some float64) float64 {
return float64(int(some*10)) / 10
}
Edited, thanks to excellent help from @icza!
This applies to my question: https://golang.org/pkg/encoding/json/#Decoder.Decode
See my revised solution, with detailed comments: https://github.com/rayning0/griddy/blob/master/controllers/default.go
Your code can be simplified in several points, and while you said "rounding" you did "truncating" in the end which is not the same.
One important thing: if an error is encountered, you should return early and not continue, as that will only be the source of additional errors or even runtime panics. See at the end.
Easier would be to use json.Decoder
, decoding right from the response body (which implements io.Reader
).
Also note to simplify parsing floats given as string
values in JSON, a better option would be to use json.Number
.
Parsing can be as simple as this:
var prices []map[string]json.Number
if err := json.NewDecoder(response.Body).Decode(&prices); err != nil {
fmt.Println(err)
return
}
Calculating sum can also be simplified: there is no need to keep track of size
, as that is simply the length of the map:
sum := 0.0
for _, p := range prices {
f, _ := p["price"].Float64()
sum += f
}
Note that if you want to handle errors in a way to simply exclude it from the sum (and not return with an error), only then would you need to count valid numbers.
Multiplying by 10 and then dividing by 10 is truncating and not rounding. For rounding, you should add 0.5
between those 2 operations. For details, see this answer: Golang Round to Nearest 0.05
So a correct rounding function that properly rounds both positive and negative numbers to arbitrary unit:
func Round(x, unit float64) float64 {
if x > 0 {
return float64(int64(x/unit+0.5)) * unit
}
return float64(int64(x/unit-0.5)) * unit
}
So the result:
avg := Round(sum/float64(len(prices)), 0.1)
Since your getAvgPrice()
can fail at multiple points, you should definitely add an error return value too.
This is the complete solution with proper error handling:
func getAvgPrice(starttime, endtime string) (float64, error) {
response, err := http.Get("https://hourlypricing.comed.com/api?type=5minutefeed&datestart=" + starttime + "&dateend=" + endtime)
if err != nil {
return 0, err
}
defer response.Body.Close()
var prices []map[string]json.Number
if err := json.NewDecoder(response.Body).Decode(&prices); err != nil {
return 0, err
}
sum := 0.0
for _, p := range prices {
f, err := p["price"].Float64()
if err != nil {
return 0, err
}
sum += f
}
return Round(sum/float64(len(prices)), 0.1), nil
}