jaxxstorm
jaxxstorm

Reputation: 13241

Confusion with array of structs in Golang

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.

Upvotes: 1

Views: 206

Answers (2)

Himanshu
Himanshu

Reputation: 12675

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

Upvotes: -1

Adrian
Adrian

Reputation: 46393

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)

Upvotes: 2

Related Questions