WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

Does anyone know why my use of URLSession doesn't return anything?

Forums > SwiftUI

I'm trying to learn how to fetch data from a JSON file hosted on 511.org into a SwiftUI project. More specifically, I am trying to take a list of Caltrain trains and write the originRef (origin reference number) and originName (name of the train's origin) in a list and display it on the screen. However, when I try this, it only displays a blank screen and provides no errors.

Here is the code I have:

struct Response: Codable {
    //array of them in the main response
    var MonitoredStopVisit: [Result]
}

struct Result: Codable {
    var OriginRef: String
    var OriginName: String
}

Here, Result stores the OriginRef and OriginName, and Response stores an array of the results.

Code for displaying the result in a SwiftUI List:

struct ContentView: View {
    @State private var results = [Result]()
    var body: some View {
        Text("test")
        List(results, id: \.OriginRef) { item in
            VStack(alignment: .leading){
                Text(item.OriginName)
                    .font(.headline)
                Text(item.OriginRef)
            }
        }

Rest of my code which creates the URL, fetches data, and decodes the result:

.task {
            await loadData() //acknowledges it might go to sleep with await
        }
    }

    func loadData() async { //this loadData method might want to go to sleep until it has done its work. mark the parts that might go to sleep with the await keyword

        guard let url = URL(string: "http://api.511.org/transit/StopMonitoring?api_key=[myKey]&agency=CT&format=json")
        else { //if url fails
            print("invalid URL")
            return
        }

        //fetch data for URL
        do { //have to use try and await because it still might need to go to sleep while it waits. Also user might not connected to interenet or something like that, so we use try
            let (data, _) = try await URLSession.shared.data(from: url) //gives us the data but ignores metadata

            if let decodedResponse = try? JSONDecoder().decode(Response.self, from:
                                                                data) {
                results = decodedResponse.MonitoredStopVisit
            }
            print("working...")

        } catch {
            print("invalid data")
        }

    }
}

Finally, here is a sample JSON file:


{
   "ServiceDelivery":{
      "ResponseTimestamp":"2022-04-14T05:19:06Z",
      "ProducerRef":"CT",
      "Status":true,
      "StopMonitoringDelivery":{
         "version":"1.4",
         "ResponseTimestamp":"2022-04-14T05:19:06Z",
         "Status":true,
         "MonitoredStopVisit":[
            {
               "RecordedAtTime":"2022-04-14T05:18:52Z",
               "MonitoringRef":"70191",
               "MonitoredVehicleJourney":{
                  "LineRef":"L1",
                  "DirectionRef":"N",
                  "FramedVehicleJourneyRef":{
                     "DataFrameRef":"2022-04-13",
                     "DatedVehicleJourneyRef":"141"
                  },
                  "PublishedLineName":"Local",
                  "OperatorRef":"CT",
                  "OriginRef":"70271",
                  "OriginName":"Tamien Caltrain Station",
                  "DestinationRef":"70011",
                  "DestinationName":"San Francisco",
                  "Monitored":true,
                  "InCongestion":null,
                  "VehicleLocation":{
                     "Longitude":"-122.12883",
                     "Latitude":"37.421051"
                  },
                  "Bearing":null,
                  "Occupancy":null,
                  "VehicleRef":"141",
                  "MonitoredCall":{
                     "StopPointRef":"70191",
                     "StopPointName":"California Avenue Caltrain Station",
                     "VehicleLocationAtStop":"",
                     "VehicleAtStop":"",
                     "AimedArrivalTime":"2022-04-14T05:18:00Z",
                     "ExpectedArrivalTime":"2022-04-14T05:18:45Z",
                     "AimedDepartureTime":"2022-04-14T05:18:00Z",
                     "ExpectedDepartureTime":"2022-04-14T05:19:45Z",
                     "Distances":""
                  }
               }
            },
            {
               "RecordedAtTime":"1970-01-01T00:00:00Z",
               "MonitoringRef":"70101",
               "MonitoredVehicleJourney":{
                  "LineRef":"L1",
                  "DirectionRef":"N",
                  "FramedVehicleJourneyRef":{
                     "DataFrameRef":"2022-04-13",
                     "DatedVehicleJourneyRef":"139"
                  },
                  "PublishedLineName":"Local",
                  "OperatorRef":"CT",
                  "OriginRef":"70261",
                  "OriginName":"San Jose Diridon Caltrain Station",
                  "DestinationRef":"70011",
                  "DestinationName":"San Francisco",
                  "Monitored":true,
                  "InCongestion":null,
                  "VehicleLocation":{
                     "Longitude":"",
                     "Latitude":""
                  },
                  "Bearing":null,
                  "Occupancy":null,
                  "VehicleRef":null,
                  "MonitoredCall":{
                     "StopPointRef":"70101",
                     "StopPointName":"Hayward Park Caltrain Station",
                     "VehicleLocationAtStop":"",
                     "VehicleAtStop":"",
                     "AimedArrivalTime":"2022-04-14T05:18:00Z",
                     "ExpectedArrivalTime":"2022-04-14T05:18:47Z",
                     "AimedDepartureTime":"2022-04-14T05:18:00Z",
                     "ExpectedDepartureTime":"2022-04-14T05:19:47Z",
                     "Distances":""
                  }
               }
            },
            {
               "RecordedAtTime":"2022-04-14T05:18:52Z",
               "MonitoringRef":"70122",
               "MonitoredVehicleJourney":{
                  "LineRef":"L1",
                  "DirectionRef":"S",
                  "FramedVehicleJourneyRef":{
                     "DataFrameRef":"2022-04-13",
                     "DatedVehicleJourneyRef":"138"
                  },
                  "PublishedLineName":"Local",
                  "OperatorRef":"CT",
                  "OriginRef":"70012",
                  "OriginName":"San Francisco Caltrain Station",
                  "DestinationRef":"70272",
                  "DestinationName":"Tamien",
                  "Monitored":true,
                  "InCongestion":null,
                  "VehicleLocation":{
                     "Longitude":"-122.300728",
                     "Latitude":"37.5412407"
                  },
                  "Bearing":null,
                  "Occupancy":null,
                  "VehicleRef":"138",
                  "MonitoredCall":{
                     "StopPointRef":"70122",
                     "StopPointName":"Belmont Caltrain Station",
                     "VehicleLocationAtStop":"",
                     "VehicleAtStop":"",
                     "AimedArrivalTime":"2022-04-14T05:15:00Z",
                     "ExpectedArrivalTime":"2022-04-14T05:19:41Z",
                     "AimedDepartureTime":"2022-04-14T05:15:00Z",
                     "ExpectedDepartureTime":"2022-04-14T05:20:41Z",
                     "Distances":""
                  }
               }
            },
            {
               "RecordedAtTime":"2022-04-14T05:18:52Z",
               "MonitoringRef":"70052",
               "MonitoredVehicleJourney":{
                  "LineRef":"L1",
                  "DirectionRef":"S",
                  "FramedVehicleJourneyRef":{
                     "DataFrameRef":"2022-04-13",
                     "DatedVehicleJourneyRef":"140"
                  },
                  "PublishedLineName":"Local",
                  "OperatorRef":"CT",
                  "OriginRef":"70012",
                  "OriginName":"San Francisco Caltrain Station",
                  "DestinationRef":"70262",
                  "DestinationName":"San Jose Diridon",
                  "Monitored":true,
                  "InCongestion":null,
                  "VehicleLocation":{
                     "Longitude":"-122.408562",
                     "Latitude":"37.6520805"
                  },
                  "Bearing":null,
                  "Occupancy":null,
                  "VehicleRef":"140",
                  "MonitoredCall":{
                     "StopPointRef":"70052",
                     "StopPointName":"San Bruno Caltrain Station",
                     "VehicleLocationAtStop":"",
                     "VehicleAtStop":"",
                     "AimedArrivalTime":"2022-04-14T05:21:00Z",
                     "ExpectedArrivalTime":"2022-04-14T05:19:13Z",
                     "AimedDepartureTime":"2022-04-14T05:21:00Z",
                     "ExpectedDepartureTime":"2022-04-14T05:21:00Z",
                     "Distances":""
                  }
               }
            },
            {
               "RecordedAtTime":"2022-04-14T05:18:52Z",
               "MonitoringRef":"70212",
               "MonitoredVehicleJourney":{
                  "LineRef":"L1",
                  "DirectionRef":"S",
                  "FramedVehicleJourneyRef":{
                     "DataFrameRef":"2022-04-13",
                     "DatedVehicleJourneyRef":"136"
                  },
                  "PublishedLineName":"Local",
                  "OperatorRef":"CT",
                  "OriginRef":"70012",
                  "OriginName":"San Francisco Caltrain Station",
                  "DestinationRef":"70262",
                  "DestinationName":"San Jose Diridon",
                  "Monitored":true,
                  "InCongestion":null,
                  "VehicleLocation":{
                     "Longitude":"-122.094978",
                     "Latitude":"37.4019585"
                  },
                  "Bearing":null,
                  "Occupancy":null,
                  "VehicleRef":"136",
                  "MonitoredCall":{
                     "StopPointRef":"70212",
                     "StopPointName":"Mountain View Caltrain Station",
                     "VehicleLocationAtStop":"",
                     "VehicleAtStop":"",
                     "AimedArrivalTime":"2022-04-14T05:21:00Z",
                     "ExpectedArrivalTime":"2022-04-14T05:18:26Z",
                     "AimedDepartureTime":"2022-04-14T05:21:00Z",
                     "ExpectedDepartureTime":"2022-04-14T05:21:00Z",
                     "Distances":""
                  }
               }
            },
            {
               "RecordedAtTime":"1970-01-01T00:00:00Z",
               "MonitoringRef":"70091",
               "MonitoredVehicleJourney":{
                  "LineRef":"L1",
                  "DirectionRef":"N",
                  "FramedVehicleJourneyRef":{
                     "DataFrameRef":"2022-04-13",
                     "DatedVehicleJourneyRef":"139"
                  },
                  "PublishedLineName":"Local",
                  "OperatorRef":"CT",
                  "OriginRef":"70261",
                  "OriginName":"San Jose Diridon Caltrain Station",
                  "DestinationRef":"70011",
                  "DestinationName":"San Francisco",
                  "Monitored":true,
                  "InCongestion":null,
                  "VehicleLocation":{
                     "Longitude":"",
                     "Latitude":""
                  },
                  "Bearing":null,
                  "Occupancy":null,
                  "VehicleRef":null,
                  "MonitoredCall":{
                     "StopPointRef":"70091",
                     "StopPointName":"San Mateo Caltrain Station",
                     "VehicleLocationAtStop":"",
                     "VehicleAtStop":"",
                     "AimedArrivalTime":"2022-04-14T05:22:00Z",
                     "ExpectedArrivalTime":"2022-04-14T05:19:55Z",
                     "AimedDepartureTime":"2022-04-14T05:22:00Z",
                     "ExpectedDepartureTime":"2022-04-14T05:22:00Z",
                     "Distances":""
                  }
               }
            }
         ]
      }
   }
}

I would appreciate any guidance on this. Thank you in advance!

   

This code:

if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
    results = decodedResponse.MonitoredStopVisit
}

hides any errors that might occur while parsing the JSON. Do this instead so you can see the errors:

do {
    let decodedResponse = try JSONDecoder().decode(Response.self, from: data)
    results = decodedResponse.MonitoredStopVisit
} catch {
    print(error)
}

PS. I would rename your Result struct since there is a built-in Result type in Swift already and you could run into name conflicts and weird errors.

1      

Thanks for your response @roosterboy

The resulting error I get is: keyNotFound(CodingKeys(stringValue: "MonitoredStopVisit", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"MonitoredStopVisit\", intValue: nil) (\"MonitoredStopVisit\").", underlyingError: nil))

I think this is because of how I am trying to decode the JSON and the format of the JSON. But I am unable to figure it out--do you have any idea how to get around this?

   

Parsing deeply nested JSON when you only want a couple of keys from the deepest level is a pain in the ass. There are a few ways to approach it.

  1. Create a different struct for each level of the JSON and let the default Decodable process handle everything. You can use resources like Quicktype.io or the app Ducky to generate the structs you would need. This is the easiest method to implement, but you end up with a lot of extraneous structs hanging around for no reason.
import Foundation

struct CaltrainResponse: Decodable {
    struct ServiceDelivery: Decodable {
        struct StopMonitoringDelivery: Decodable {
            struct MonitoredStopVisit: Decodable {

                let caltrainResult: CaltrainResult

                enum CodingKeys: String, CodingKey {
                    case caltrainResult = "MonitoredVehicleJourney"
                }
            }

            let monitoredStopVisits: [MonitoredStopVisit]

            enum CodingKeys: String, CodingKey {
                case monitoredStopVisits = "MonitoredStopVisit"
            }
        }

        let stopMonitoringDelivery: StopMonitoringDelivery

        enum CodingKeys: String, CodingKey {
            case stopMonitoringDelivery = "StopMonitoringDelivery"
        }
    }

    let serviceDelivery: ServiceDelivery

    enum CodingKeys: String, CodingKey {
        case serviceDelivery = "ServiceDelivery"
    }

    //convenience property so we don't have to use that long chain of nested properties
    var results: [CaltrainResult] {
        serviceDelivery.stopMonitoringDelivery.monitoredStopVisits.map({$0.caltrainResult})
    }
}

struct CaltrainResult: Decodable {
    let originRef: String
    let originName: String

    enum CodingKeys: String, CodingKey {
        case originRef = "OriginRef"
        case originName = "OriginName"
    }
}

let caltrainData = caltrainJSON.data(using: .utf8)!

do {
    let response = try JSONDecoder().decode(CaltrainResponse.self, from: caltrainData)
    let results = response.results
    print(results)
} catch {
    print(error)
}
  1. You can manually decode the JSON by drilling down using nestedContainers and nestedUnkeyedContainers to unravel the data.
import Foundation

struct CaltrainResponse: Decodable {
    enum ServiceDeliveryKeys: String, CodingKey {
        case ServiceDelivery
    }

    enum StopMonitoringDeliveryKeys: String, CodingKey {
        case StopMonitoringDelivery
    }

    enum MonitoredStopVisitKeys: String, CodingKey {
        case MonitoredStopVisit
    }

    enum MonitoredVehicleJourneyKeys: String, CodingKey {
        case MonitoredVehicleJourney
    }

    let results: [CaltrainResult]

    init(from decoder: Decoder) throws {
        let serviceDeliveryContainer = try decoder.container(keyedBy: ServiceDeliveryKeys.self)
        let stopMonitoringDeliveryContainer = try serviceDeliveryContainer.nestedContainer(keyedBy: StopMonitoringDeliveryKeys.self, forKey: .ServiceDelivery)
        let monitoredStopVisitsContainer = try stopMonitoringDeliveryContainer.nestedContainer(keyedBy: MonitoredStopVisitKeys.self, forKey: .StopMonitoringDelivery)
        var visits: [CaltrainResult] = []
        var monitoredVisits = try monitoredStopVisitsContainer.nestedUnkeyedContainer(forKey: .MonitoredStopVisit)
        while !monitoredVisits.isAtEnd {
            let stopVisitContainer = try monitoredVisits.nestedContainer(keyedBy: MonitoredVehicleJourneyKeys.self)
            visits.append(try stopVisitContainer.decode(CaltrainResult.self, forKey: .MonitoredVehicleJourney))
        }
        self.results = visits
    }
}

struct CaltrainResult: Decodable {
    var originRef: String
    var originName: String

    enum CodingKeys: String, CodingKey {
        case originRef = "OriginRef"
        case originName = "OriginName"
    }
}

let caltrainData = caltrainJSON.data(using: .utf8)!

do {
    let response = try JSONDecoder().decode(CaltrainResponse.self, from: caltrainData)
    print(response.results)
} catch {
    print(error)
}
  1. You can forgo Codable and use the older JSONSerialization API.
import Foundation

struct CaltrainResult: Decodable {
    var originRef: String
    var originName: String
}

extension CaltrainResult {
    init?(from dict: [String: Any]) {
        guard let journey = dict["MonitoredVehicleJourney"] as? [String: Any],
              let name = journey["OriginName"] as? String,
              let ref = journey["OriginRef"] as? String else {
                  return nil
              }

        self.originRef = ref
        self.originName = name
    }
}

let caltrainData = caltrainJSON.data(using: .utf8)!

var results: [CaltrainResult] = []
do {
    if let response = try JSONSerialization.jsonObject(with: caltrainData, options: []) as? [String: Any] {
        if let serviceDelivery = response["ServiceDelivery"] as? [String: Any],
           let stopMonitoringDelivery = serviceDelivery["StopMonitoringDelivery"] as? [String: Any],
           let monitoredStopVisit = stopMonitoringDelivery["MonitoredStopVisit"] as? [[String: Any]] {
            results = monitoredStopVisit.compactMap { CaltrainResult(from: $0) }
            print(results)
        }
    }
} catch {
    print(error)
}

   

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.