0

I am building an application that reads information from the London Underground API. I'm struggling on parsing the GET request into something readable and where the user can access specific line information.

Here is my current code, I'm using a struct to store the response from the GET request after Unmarshaling it.

// struct for decoding into a structure
    var tubeStatuses struct {
        object []struct {
            typeDef      []string `json:"$type"`
            idName       string   `json:"id"`
            name         string   `json:"name"`
            modeName     string   `json:"modeName"`
            disruptions  string   `json:"disruption"`
            created      string   `json:"created"`
            modified     string   `json:"modified"`
            statusObject []struct {
                zeroObject []struct {
                    typeDef        string `json:"$type"`
                    id             int    `json:"id"`
                    statusSeverity int    `json:"statusSeverity"`
                    statusDesc     string `json:"statusSeverityDescription"`
                    created        string `json:"created"`
                    validity       string `json:"validityPeriods"`
                }
            }
            route         string `json:"routeSections"`
            serviceObject []struct {
                zeroObject []struct {
                    typeDef string `json:"$type"`
                    name    string `json:"name"`
                    uri     string `json:"uri"`
                }
            }
            crowdingObject []struct {
                typeDef string `json:"$type"`
            }
        }
    }

    fmt.Println("Now retrieving Underground line status, please wait...")
    // two variables (response and error) which stores the response from e GET request
    getRequest, err := http.Get("https://api.tfl.gov.uk/line/mode/tube/status")
    fmt.Println("The status code is", getRequest.StatusCode, http.StatusText(getRequest.StatusCode))

    if err != nil {
        fmt.Println("Error!")
        fmt.Println(err)
    }

    //close - this will be done at the end of the function
    // it's important to close the connection - we don't want the connection to leak
    defer getRequest.Body.Close()

    // read the body of the GET request
    rawData, err := ioutil.ReadAll(getRequest.Body)

    if err != nil {
        fmt.Println("Error!")
        fmt.Println(err)
    }

    jsonErr := json.Unmarshal(rawData, &tubeStatuses)

    if jsonErr != nil {
        fmt.Println(jsonErr)
    }

    //test
    fmt.Println(tubeStatuses.object[0].name)

    fmt.Println("Welcome to the TfL Underground checker!\nPlease enter a number for the line you want to check!\n0 - Bakerloo\n1 - central\n2 - circle\n3 - district\n4 - hammersmith & City\n5 - jubilee\n6 - metropolitan\n7 - northern\n8 - piccadilly\n9 - victoria\n10 - waterloo & city")

The error I see is the following:

json: cannot unmarshal array into Go value of type struct { object []struct { typeDef []string "json:\"$type\""; idName string "json:\"id\""; name string "json:\"name\""; modeName string "json:\"modeName\""; disruptions string "json:\"disruption\""; created string "json:\"created\""; modified string "json:\"modified\""; statusObject []struct { zeroObject []struct { typeDef string "json:\"$type\""; id int "json:\"id\""; statusSeverity int "json:\"statusSeverity\""; statusDesc string "json:\"statusSeverityDescription\""; created string "json:\"created\""; validity string "json:\"validityPeriods\"" } }; route string "json:\"routeSections\""; serviceObject []struct { zeroObject []struct { typeDef string "json:\"$type\""; name string "json:\"name\""; uri string "json:\"uri\"" } }; crowdingObject []struct { typeDef string "json:\"$type\"" } } }

How do I unmarshal the array into something readable?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
JoshBl_
  • 51
  • 1
  • 6
  • I've added an answer below, but also recommend a review of the golang formatting and naming standards, found here: https://golang.org/doc/effective_go.html Go is an awesome language. Have fun! – jfarleyx Jan 11 '20 at 22:53

2 Answers2

3

Below is a working version of your code. However, there are multiple issues you'll want to address.

  1. As mentioned elsewhere in this thread, you should make TubeStatuses (note capitalization) a type and an array. The field names should also be capitalized so that they are exported.

  2. You weren't accounting for all of the output and in some cases the type was wrong. For example, you were missing an object called "Disruption". I've added it in my example. "Crowding" declaration was of the wrong type. Also, "route" aka "routeSections" is not a string. It's another array, likely of objects, but there was no output from the API for me to determine what routeSections actually consists of. So, as a workaround, I declared that as a type of "json.RawMessage" to allow it to be unmarshalled as a byte slice. See this for more details: https://golang.org/pkg/encoding/json/#RawMessage

  3. You can't use json: "$type"`. Specifically, the dollar sign isn't allowed.

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

type TubeStatuses struct {
    TypeDef      []string `json:"type"`
    IDName       string   `json:"id"`
    Name         string   `json:"name"`
    ModeName     string   `json:"modeName"`
    Disruptions  string   `json:"disruption"`
    Created      string   `json:"created"`
    Modified     string   `json:"modified"`
    LineStatuses []struct {
        Status []struct {
            TypeDef                   string `json:"type"`
            ID                        int    `json:"id"`
            StatusSeverity            int    `json:"statusSeverity"`
            StatusSeverityDescription string `json:"statusSeverityDescription"`
            Created                   string `json:"created"`
            ValidityPeriods           []struct {
                Period struct {
                    TypeDef  string `json: "type"`
                    FromDate string `json: "fromDate"`
                    ToDate   string `json: "toDate"`
                    IsNow    bool   `json: "isNow"`
                }
            }
            Disruption struct {
                TypeDef             string   `json: "type"`
                Category            string   `json: "category"`
                CategoryDescription string   `json: "categoryDescription"`
                Description         string   `json: "description"`
                AdditionalInfo      string   `json: "additionalInfo"`
                Created             string   `json: "created"`
                AffectedRoutes      []string `json: "affectedRoutes"`
                AffectedStops       []string `json: "affectedStops"`
                ClosureText         string   `json: closureText"`
            }
        }
    }
    RouteSections json.RawMessage `json: "routeSections"`
    ServiceTypes  []struct {
        Service []struct {
            TypeDef string `json:"type"`
            Name    string `json:"name"`
            URI     string `json:"uri"`
        }
    }
    Crowding struct {
        TypeDef string `json:"type"`
    }
}

func main() {
    fmt.Println("Now retrieving Underground line status, please wait...")

    // two variables (response and error) which stores the response from e GET request
    getRequest, err := http.Get("https://api.tfl.gov.uk/line/mode/tube/status")
    if err != nil {
        fmt.Println("Error!")
        fmt.Println(err)
    }

    fmt.Println("The status code is", getRequest.StatusCode, http.StatusText(getRequest.StatusCode))

    //close - this will be done at the end of the function
    // it's important to close the connection - we don't want the connection to leak
    defer getRequest.Body.Close()

    // read the body of the GET request
    rawData, err := ioutil.ReadAll(getRequest.Body)

    if err != nil {
        fmt.Println("Error!")
        fmt.Println(err)
    }
    ts := []TubeStatuses{}

    jsonErr := json.Unmarshal(rawData, &ts)

    if jsonErr != nil {
        fmt.Println(jsonErr)
    }

    //test
    fmt.Println(ts[0].Name)

    fmt.Println("Welcome to the TfL Underground checker!\nPlease enter a number for the line you want to check!\n0 - Bakerloo\n1 - central\n2 - circle\n3 - district\n4 - hammersmith & City\n5 - jubilee\n6 - metropolitan\n7 - northern\n8 - piccadilly\n9 - victoria\n10 - waterloo & city")
}

Output...

code output

jfarleyx
  • 775
  • 1
  • 5
  • 9
2

You need an array of that struct:

var tubeStatuses []struct  {...}

Also, your struct member names are not exported, so they will not be unmarshaled even after you do this. Capitalize the names.

Your struct is large, so consider making it a type.

Burak Serdar
  • 46,455
  • 3
  • 40
  • 59