af83

Parsing API responses in Go

Go is a convenient language for using the Restful API. For example, Aéroports de Paris asked us to make a kiosk to help travellers find their way from Orly airport to their final destination within the Île de France area. The itineraries are gathered from several APIs by the backend, which is written in Go.

The kiosk, at Orly airport

The traveller inputs some data, most importantly their destination. From that, the device makes a call to the Google API to geolocalize the destination more precisely. Then, the backend makes some calls to the STIF API to fetch itineraries via public transport. At the same time, the backend also calls other APIs to evaluate an itinerary via taxi.

For the itineraries from STIF, the first strategy is to try with 3 different criteria and then, if the returned itineraries are too simlar, to add some constraints and try again. Go is very helpful for this: with goroutines, it's easy to make the HTTP calls in parallel and compare the results when all the goroutines are done. But I'd like to talk about another aspect of the project, parsing the responses.

The responses from STIF are big XMLs containing a lot of data, most of which is not relevant for our use cases. The most direct way to parse an XML response like this is to use encoding/xml. But in our case, we want to access embedded data in deep structures and I was too lazy to declare all the structs for that. So instead, I chose to use Gokogiri, the golang binding for libxml2.

For example, the backend can make a call to estimate the cost of a fare. The response is composed of one <Fare> tag, and inside it, one or more <Cost> tags (one per section) amongst other things. Our code is simple:

response, err := http.Get(uri)
if err != nil {
  return
}
defer response.Body.Close()

data, _ := ioutil.ReadAll(response.Body)
doc, err := gokogiri.ParseXml(data)
if err != nil {
  return
}
defer doc.Free()

costs, err := doc.Root().Search("Fare/Cost")
if err != nil {
  return
}

fare := 0.0
for _, cost := range costs {
  c, _ := strconv.ParseFloat(cost.Content(), 64)
  fare += c
}

For the same reason, we preferred to avoid encoding/json for parsing complex JSON responses. I used jsonq, and you can see how easy it is to explore a JSON response from Google with several levels of depth:

response, err := http.Get(uri)
if err != nil {
  return
}
defer response.Body.Close()

resp := make(map[string]interface{})
body, _ := ioutil.ReadAll(response.Body)
err = json.Unmarshal(body, &resp)
if err != nil {
  return
}

jq := jsonq.NewQuery(resp)
status, err := jq.String("status")
if err != nil {
  return
}
if status != "OK" {
  err = errors.New(status)
  return
}

normalized.Lat, err = jq.Float("result", "geometry", "location", "lat")
if err != nil {
  return
}
normalized.Lng, err = jq.Float("result", "geometry", "location", "lng")
if err != nil {
  return
}

I really think Golang is a very good match for our project and, even though it's a statically typed language, it's easy to parse to the JSON and XML responses from the Restful API to extract the relevant data.

blog comments powered by Disqus