Parsing RATP's json API with Golang

PUBLISHED ON DEC 3, 2019 — 700 words — DEV , GOLANG


Accessing the RAPT’s API.

If you want a direct access to RATP’s open data API, you’ll need to fill a pdf form and send it to an email address. I tried and never heard back of them. So I used https://api-ratp.pierre-grimaud.fr in order to access real time schedules. Thank-you to Pierre Grimaud for providing this really cool alternative. The documentation of the API is pretty straightforward and it’s only a matter of finding the right station along its slug name. For example:
curl -X GET "https://api-ratp.pierre-grimaud.fr/v4/stations/buses/58"

Creating the json struct for Golang

Maybe the most annoying part of using json with golang is that you have to describe the json as a struct (there are other way but they are less golangish). Because I’m lazy, I used https://mholt.github.io/json-to-go/ in order to convert a json output to the struct.

The source code

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"time"
)

func main() {

	// Structure to store the configuration
	// we use a structure so it will be easier to later add support for a configuration file for example
	// Check https://api-ratp.pierre-grimaud.fr/v4/ for the parameters
	type gobus struct {
		mode      string // buses, metro,…
		line      string // Line number or name
		slug      string // Stop slug: curl -X GET "https://api-ratp.pierre-grimaud.fr/v4/stations/buses/58"
		fancy     string // Fancy display name if you want
		direction string // A or R (Aller or Retour)
		wait      int    // How long do we wait between requests
	}

	type results struct { // Structure where we store the display and eventual success or fail
		success bool
		line1   string
		line2   string
		prev1   string
		prev2   string
	}
	// json structure return by the API
	// struct was automatically created from an output using https://mholt.github.io/json-to-go/
	type ratp struct {
		Result struct {
			Schedules []struct {
				Message     string `json:"message"`
				Destination string `json:"destination"`
			} `json:"schedules"`
		} `json:"result"`
		Metadata struct {
			Call    string    `json:"call"`
			Date    time.Time `json:"date"`
			Version int       `json:"version"`
		} `json:"_metadata"`
	}

	var conf gobus // URL construction
	conf.mode = "buses"
	conf.line = "58"
	conf.slug = "Didot"
	conf.direction = "R"
	//url to fetch that contains the line, stop and direction
	url := fmt.Sprintf("https://api-ratp.pierre-grimaud.fr/v4/schedules/%s/%s/%s/%s",
		conf.mode,
		conf.line,
		conf.slug,
		conf.direction)

	for { //let's do an infinite loop

		var record ratp     //Let's store the decoded JSON in the record variable
		var display results //We will construct the result display messages here

		display.success = false //Until proven, we failed at getting results
		display.line1 = "Error while decoding the json"
		display.line2 = "Error while decoding the json"
		display.prev1 = "None"
		display.prev2 = "None"

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			display.success = false
		}

		client := &http.Client{}
		resp, err := client.Do(req)
		if err != nil {
			display.success = false
		} else {
			display.success = true
		}

		if display.success { //We only continue if we succeeded the http request
			defer resp.Body.Close()

			if resp.StatusCode != 200 { //if we don't get a 200 status code, then something went wrong server side
				display.success = false
			}

			if err := json.NewDecoder(resp.Body).Decode(&record); err != nil { //We might get corrupted
				display.success = false //or partial json
			}

			//If schedules are unavailable (various reasons why the server might return this)
			if len(record.Result.Schedules) == 1 && record.Result.Schedules[0].Message == "Schedules unavailable" {
				display.success = false
			}

			//If we have 2 results then we know the answer is ok
			//Note: for 2 values, loops are a bit overkill so…
			if len(record.Result.Schedules) == 2 {
				display.success = true
				display.line1 = record.Result.Schedules[0].Message
				display.line2 = record.Result.Schedules[1].Message
				display.prev1 = display.line1
				display.prev2 = display.line2

			}
		} //display.success == true

		c := exec.Command("clear") // we clear the screen
		c.Stdout = os.Stdout
		c.Run()
		/* ===== Message display ===== */
		fmt.Print("========== Next Bus [")
		fmt.Print(time.Now().Format("15:04:05"))
		fmt.Println("] ==========")
		if display.success == true {
			fmt.Println(display.line1)
			fmt.Println(display.line2)
		} else {
			fmt.Println("Error, will try again in 60s")
			fmt.Println("Previous was: ", display.prev1)
			fmt.Println("Previous was: ", display.prev2)

		} //else
		fmt.Println("=========================================")
		time.Sleep(time.Second * 60)
	} //for

} //main

Final thoughs

While the programm is not perfect and can easily be perfected, it was a cool “evening” project done while watching tv. The most important thing is that it fails gracefully if the API is not available.

TAGS: API , DEV , GOLAND , RATP