0

I am having a bit of trouble trying to get data return from the MTA API (subway system in New York) (Link Here). The data is in GTFS, so I am using a parser that is set up in a virtual environment that converts the data to JSON and sends it to me (sample JSON posted later in post). Basically, the decoding of GTFS to JSON is completed.

Now that I have the JSON data, I need to get it to display in my app. I have dealt with APIs before, but not one with this many "layers".

The question is: how should I structure my code on the Swift side so I can access each of the datapoints sent in the JSON?

Below is what I am working. The data is not displaying at all in my ContentView. I am guessing this has to do with my model being structured incorrectly, therefore the data I am attempting to display not actually being there:

struct TrainResponse: Codable {
let data: [String]
let n, s: [NS]
let id: String
let lastUpdate: Date
let location: [Double]
let name: String
let routes: [String]
let stops: [String: [Double]]

enum CodingKeys: String, CodingKey {
    case data
    case n = "N"
    case s = "S"
    case id
    case lastUpdate = "last_update"
    case location, name, routes, stops
 }
}

// MARK: - N
struct NS: Codable {
let route: String
let time: Date
}


class API: ObservableObject {

@Published var storedData = String()
@Published var ns = NS.self
@Published var id = String()
@Published var lastUpdate = Date()
@Published var location = [Double]()
@Published var name = String()
@Published var routes = [String]()
@Published var stops = [String: [Double]]()

func loadData() {
    guard let url = URL(string: "http://127.0.0.1:5000/by-route/A") else {
        print("Your API end point is Invalid")
        return
    }
    let request = URLRequest(url: url)
    URLSession.shared.dataTask(with: request) { data, response, error in
        if let data = data {
            if let response = try? JSONDecoder().decode(TrainResponse.self, from: data) {
                DispatchQueue.main.async {
                    if let data = response.data.first {
                        self.storedData = data
                        
                        self.routes = response.routes
                        self.id = response.id
                        self.lastUpdate = response.lastUpdate
                        self.location = response.location
                        self.name = response.name
                        self.routes = response.routes
                        self.stops = response.stops
                    }
                }
                return
            }
        }
    }.resume()
  }
}

Below is a sample of the JSON - It repeats what you see below hundreds of times, but I closed it off in the sample as if it were a complete JSON object (to make it easier to debug)

{
"data": [
    {
        "N": [
            {
                "route": "A",
                "time": "2021-07-21T21:33:00-04:00"
            }
        ],
        "S": [
            {
                "route": "A",
                "time": "2021-07-21T21:34:40-04:00"
            }
        ],
        "id": "6d6a",
        "last_update": "2021-07-21T21:25:06-04:00",
        "location": [
            40.681711,
            -73.837683
        ],
        "name": "104 St",
        "routes": [
            "A"
        ],
        "stops": {
            "A63": [
                40.681711,
                -73.837683
            ]
        }
    }
    ],
    "updated": "2021-07-21T21:25:06-04:00"
}

I am really at wits end here. Any thoughts on how my code should be restructured to return the data correctly?

I know I should be nesting the data (e.g. self.routes = data["N"].etc.etc but I can't quite seem to figure out how to structure my code so I can do that. Once that is done, I should have no trouble displaying the data in the app.. just need to get the data into the app first!

EDIT - Adding code for contentview

import SwiftUI

struct ContentView: View {

@StateObject var api = API()

var body: some View {
    Text("yo")
    
    List {
        ForEach(api.routes, id: \.self) { index in
            Text(index)
        }
    }
    .onAppear { api.loadData() }
    
  }
}

EDIT 2 I have updated the data models. They now look like the following:

    struct Main: Codable {
    let data: [Datum]
    let updated: String
}

// MARK: - Datum
struct Datum: Codable {
    let n, s: [N]
    let id: String
    let lastUpdate: Date
    let location: [Double]
    let name: String
    let routes: [String]
    let stops: [String: [Double]]

    enum CodingKeys: String, CodingKey {
        case n = "N"
        case s = "S"
        case id
        case lastUpdate = "last_update"
        case location, name, routes, stops
    }
}

// MARK: - N
struct N: Codable {
    let route: String
    let time: Date
}
7
  • Take a look at quicktype.io it can help see things you might have missed with your struct. Commented Jul 22, 2021 at 3:43
  • That is what I used originally to generate the struct. I made some changes to it because it was not taking into account the initial "data:" piece in the JSON. Do you think the QuickType struct is completely correct? Commented Jul 22, 2021 at 3:51
  • could you show the code you use to display the data in ContentView. Also swift/apple likes only "https" Commented Jul 22, 2021 at 3:53
  • @workingdog I have added the code to the post. It is very simple, as I want to confirm I can get the data to return before I start building out the view. Commented Jul 22, 2021 at 3:58
  • 1
    do you get anything from your server. If you add "print("\n-------> data: (data) error: (error)\n")" just after "URLSession.shared.dataTask(...)". What do you get? Hard to debug without an endpoint to connect to. Commented Jul 22, 2021 at 4:18

2 Answers 2

1

this works for me:

EDIT:

struct GTFSObject: Codable {
    var data: [TrainResponse]   // <--- var
    let updated: String?
}

struct TrainResponse: Codable, Identifiable {
    let n, s: [NS]?
    let id: String?
    let lastUpdate: String?
    let location: [Double]?
    let name: String?
    let routes: [String]?
    let stops: [String: [Double]]?
    
    enum CodingKeys: String, CodingKey {
        case n = "N"
        case s = "S"
        case id
        case lastUpdate = "last_update"
        case location, name, routes, stops
    }
}

struct NS: Codable {
    let route: String?
    let time: String?
}

   class API: ObservableObject {
    
    @Published var storedData = GTFSObject(data: [], updated: nil)
    
    func loadData() {
        guard let url = URL(string: "https://api.jsonbin.io/b/60f8f8be99892a4ae9a79828") else {
            print("Your API end point is Invalid")
            return
        }
        let request = URLRequest(url: url)
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let data = data {
                if let response = try? JSONDecoder().decode(GTFSObject.self, from: data) {
                   // print("\n-------> response: \(response)\n")
                    DispatchQueue.main.async {
                        self.storedData.data = response.data
                    }
                    return
                }
            }
        }.resume()
    }
}

struct ContentView: View {
    
    @StateObject var api = API()
    
    var body: some View {
        Text("yo")
        List {
            ForEach(api.storedData.data, id: \.id) { train in
                Text(train.name ?? "no name")
            }
        }
        .onAppear { api.loadData() }
    }
}
Sign up to request clarification or add additional context in comments.

8 Comments

Nice! I tested on my end with my local server and the data returned successfully. Any ideas on how to properly store the data in each of the variables. E.g. return the data nested inside "N" within the JSON object.
This is excellent. Thank you! I will work at trying to retrieve the remaining data needed. If more question come up, I will post as a new question as I do not want to overextend on the original ask of this post.
The code completely stopped working today. Did not make any changes. Any thoughts? I have posted a new link for the JSON - is it working on your end? For clarity, it looks like the array is coming back blank. JSON: api.jsonbin.io/b/60fa28f899892a4ae9a85e78
looks like I had a problem with my server. Another question: any idea as how to access the data in the NS struct? I am trying to return the route, but swift is not happy with any of my approaches.
something like this: " if let northRoutes = train.n { ForEach(northRoutes, id: \.self) { ns in Text(ns.route ?? "no route") } }" and make NS Hashable
|
0

According to your JSON Your Swift Struct DataModel Should be like this.

struct GTFSObject: Codable {
    let data : [TrainResponse]?
    let updated : String?
    
    enum CodingKeys: String, CodingKey {
        
        case data = "data"
        case updated = "updated"
    }
}

struct TrainResponse : Codable {
    let n : [N]?
    let s : [S]?
    let id : String?
    let last_update : String?
    let location : [Double]?
    let name : String?
    let routes : [String]?
    let stops : Stops?
    
    enum CodingKeys: String, CodingKey {
        
        case n = "N"
        case s = "S"
        case id = "id"
        case last_update = "last_update"
        case location = "location"
        case name = "name"
        case routes = "routes"
        case stops = "stops"
    }
}

struct N : Codable {
    let route : String?
    let time : String?
    
    enum CodingKeys: String, CodingKey {
        
        case route = "route"
        case time = "time"
    }
}

struct S : Codable {
    let route : String?
    let time : String?
    
    enum CodingKeys: String, CodingKey {
        
        case route = "route"
        case time = "time"
    }
}

struct Stops : Codable {
    let a63 : [Double]?
    
    enum CodingKeys: String, CodingKey {
        
        case a63 = "A63"
    }
}

2 Comments

I am hosting the full JSON here: api.jsonbin.io/b/60f8f8be99892a4ae9a79828 . If you plug the full JSON into QuickType, it formats it a bit differently.
@nickreps as per your JSON and full JSON Link the Following Model is Correct i have checked.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.