I am trying to interact with a JSON API. It has two endpoints:
GetTravelTimeAsJSON - specify a traveltime ID and it returns a single traveltime GetTravelTimesAsJSON - returns an array with all the above TravelTimes.
So I have a struct like so:
type TravelTime struct {
AverageTime int `json:"AverageTime"`
CurrentTime int `json:"CurrentTime"`
Description string `json:"Description"`
Distance float64 `json:"Distance"`
EndPoint struct {
Description string `json:"Description"`
Direction string `json:"Direction"`
Latitude float64 `json:"Latitude"`
Longitude float64 `json:"Longitude"`
MilePost float64 `json:"MilePost"`
RoadName string `json:"RoadName"`
} `json:"EndPoint"`
Name string `json:"Name"`
StartPoint struct {
Description string `json:"Description"`
Direction string `json:"Direction"`
Latitude float64 `json:"Latitude"`
Longitude float64 `json:"Longitude"`
MilePost float64 `json:"MilePost"`
RoadName string `json:"RoadName"`
} `json:"StartPoint"`
TimeUpdated string `json:"TimeUpdated"`
TravelTimeID int `json:"TravelTimeID"`
}
If I call the API like so for a single travel time, I get a populated struct (I'm using this req lib)
header := req.Header{
"Accept": "application/json",
"Accept-Encoding": "gzip",
}
r, _ := req.Get("http://www.wsdot.com/Traffic/api/TravelTimes/TravelTimesREST.svc/GetTravelTimeAsJson?AccessCode=<redacted>&TravelTimeID=403", header)
var foo TravelTime
r.ToJSON(&foo)
dump.Dump(foo)
If I dump the response, it looks like this:
TravelTime {
AverageTime: 14 (int),
CurrentTime: 14 (int),
Description: "SB I-5 Pierce King County Line To SR 512",
Distance: 12.06 (float64),
EndPoint: {
Description: "I-5 @ SR 512 in Lakewood",
Direction: "S",
Latitude: 47.16158351 (float64),
Longitude: -122.481133 (float64),
MilePost: 127.35 (float64),
RoadName: "I-5"
},
Name: "SB I-5, PKCL To SR 512",
StartPoint: {
Description: "I-5 @ Pierce King County Line",
Direction: "S",
Latitude: 47.255624 (float64),
Longitude: -122.33113 (float64),
MilePost: 139.41 (float64),
RoadName: "I-5"
},
TimeUpdated: "/Date(1532707200000-0700)/",
TravelTimeID: 403 (int)
}
Now, what I want to do is have a struct for ALL responses, which is a slice of the TravelTime
struct, so I did this:
type TravelTimesResponse struct {
TravelTime []TravelTime
}
However, when I call the GetTravelTimesAsJSON
endpoint, and change it so:
var foo TravelTimesResponse
I get back 180 (the number of results) empty sets like this:
{
TravelTime: TravelTime {
AverageTime: 0 (int),
CurrentTime: 0 (int),
Description: "",
Distance: 0 (float64),
EndPoint: {
Description: "",
Direction: "",
Latitude: 0 (float64),
Longitude: 0 (float64),
MilePost: 0 (float64),
RoadName: ""
},
Name: "",
StartPoint: {
Description: "",
Direction: "",
Latitude: 0 (float64),
Longitude: 0 (float64),
MilePost: 0 (float64),
RoadName: ""
},
TimeUpdated: "",
TravelTimeID: 0 (int)
}
The JSON is here: https://gist.github.com/jaxxstorm/0ab818b300f65cf3a46cc01dbc35bf60
It works if I modify the original TravelTime
struct to be a slice like so:
type TravelTimes []struct {
}
but then it doesn't work as a single response.
I've managed to do this before, but for some reason the reason this failing is failing my brain. Any help appreciated.
Change the struct TravelTimesResponse
field name TravelTime
which is similar to the struct
name TravelTime
and then try to unmarshal the JSON.
type TravelTimesResponse struct {
TravelTime []TravelTime
}
Above should have different field name as:
type TravelTimesResponse struct {
TravelTimeData []TravelTime
}
[Edited]
The library is using interface{}
directly to unmarshal the response
// ToJSON convert json response body to struct or map
func (r *Resp) ToJSON(v interface{}) error {
data, err := r.ToBytes()
if err != nil {
return err
}
return json.Unmarshal(data, v)
}
Given JSON is an array of objects. And you are parsing it to struct with field name which will be an object key. Either create a slice of struct or implement unmarshal.
Unmarshal stores one of these in the interface value:
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
Better is to use Go package for encoding/json
.
Try Working Code on Go playground
The unmarshal isn't working because it's expecting the top level to be an object with a field TravelTime
, when the top level is actually the array of objects. You just want to unmarshal directly into a slice of objects, like so:
var foo []TravelTime
r.ToJSON(&foo)