-1

This is the error I get

Invalid data: typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "Services", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "NextBus", intValue: nil), CodingKeys(stringValue: "EstimatedArrival", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))

When I try to print the estimatedArrival in the SwiftUI View as a Text() converted from the date format 2022-08-29T12:06:28+08:00 which is in "yyyy-MM-dd’T’HH:mm:ssZ" format.

However when I change to estimatedArrival: String instead of Date, it doesn't the error anymore.

import SwiftUI

import Foundation

// MARK: - Welcome
struct BusTimings: Codable {
    let busStopCode: String
    let services: [Service]

    enum CodingKeys: String, CodingKey {
        case busStopCode = "BusStopCode"
        case services = "Services"
    }
}

// MARK: - Service
struct Service: Codable {
    let serviceNo, serviceOperator: String
    let nextBus, nextBus2, nextBus3: NextBus

    enum CodingKeys: String, CodingKey {
        case serviceNo = "ServiceNo"
        case serviceOperator = "Operator"
        case nextBus = "NextBus"
        case nextBus2 = "NextBus2"
        case nextBus3 = "NextBus3"
    }
}

// MARK: - NextBus
struct NextBus: Codable {
    let originCode, destinationCode: String
    let estimatedArrival: Date
    let latitude, longitude, visitNumber, load: String
    let feature, type: String

    enum CodingKeys: String, CodingKey {
        case originCode = "OriginCode"
        case destinationCode = "DestinationCode"
        case estimatedArrival = "EstimatedArrival"
        case latitude = "Latitude"
        case longitude = "Longitude"
        case visitNumber = "VisitNumber"
        case load = "Load"
        case feature = "Feature"
        case type = "Type"
    }
}




struct StopView: View {
    let item: BusStop
    let dateFormatter = DateFormatter()
    
    @State private var value = [Service]()
    //@State private var busStop = [BusStop]()
    //var locations = [Locations]()
    var body: some View {
        
        List(value, id: \.serviceNo) { item in
            NavigationLink(destination:Text(item.serviceNo)) {
                VStack(alignment: .leading) {
                    Text(item.serviceNo)
                        .font(.largeTitle)
                    Text(dateFormatter.string(from: item.nextBus.estimatedArrival))
                       // .bold()

                }
            }
        }
        .task {
            await loadData()
        }
        .navigationTitle(item.Description)
        .navigationBarTitleDisplayMode(.inline)
        
    }
    
    //let url = Bundle.main.url(forResource: "data", withExtension: "json")!

    
    func loadData() async {
        let busStopCode = item.BusStopCode
        let accountKey = "xxx"
        guard let url = URL(string: String("http://datamall2.mytransport.sg/ltaodataservice/BusArrivalv2" + "?AccountKey=" + accountKey + "&BusStopCode=" + busStopCode))
        else { print("Invalid URL")
            return
        }
        
        
        //print(dateFormatter.string(from: Date.now))

        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.addValue(accountKey, forHTTPHeaderField: "AccountKey")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        
        do {
            let (data, _) = try await URLSession.shared.data(for: request) // <-- here
            //print(String(data: data, encoding: .utf8)) // <-- here
            let decodedResponse = try JSONDecoder().decode(BusTimings.self, from: data)
            value = decodedResponse.services
        } catch {
            print("Invalid data: \(error)")
        }
    }
}

Thank you for reading through this! I appreciate your help!

1 Answers1

1

try this decoder (in loadData()) or some variation of it, to decode your String date into a real Date:

do {
    let (data, _) = try await URLSession.shared.data(for: request)
    let decoder = JSONDecoder() // <-- here
    decoder.dateDecodingStrategy = .formatted(formatter) // <-- here
    let decodedResponse = try decoder.decode(BusTimings.self, from: data)
    value = decodedResponse.services
} catch {
    print("Invalid data: \(error)")
}

Where you have:

let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX") // <-- todo
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"

Your date format "yyyy-MM-dd'T'HH:mm:ssZ" looks like iso8601, so you can also try:

 decoder.dateDecodingStrategy = .iso8601

EDIT-1: the test code that shows my answer works with the official data.

from the official website: https://datamall.lta.gov.sg/content/datamall/en/dynamic-data.html using the Bus Arrival data, downloaded as json and shown here as example.

Here is the test code that shows, that both the

  decoder.dateDecodingStrategy = .iso8601

and the

 decoder.dateDecodingStrategy = .formatted(formatter)

can decode the EstimatedArrival dates from the official data set.

Using

  formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"

also works.

struct ContentView: View {
    @State private var services = [Service]()
    
    let formatter: DateFormatter = {
        let frmt = DateFormatter()
        frmt.timeZone = TimeZone(identifier: "Asia/Singapore")
        frmt.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" // "yyyy-MM-dd'T'HH:mm:ssZ" also works
        return frmt
    }()
    
    var body: some View {
        NavigationView {
            List(services, id: \.serviceNo) { service in
                NavigationLink(destination: Text(service.serviceNo) ) {
                    VStack(alignment: .leading) {
                        Text(service.serviceNo).font(.largeTitle)
                        Text(formatter.string(from: service.nextBus.estimatedArrival)).bold()
                    }
                }
            }
        }
        .onAppear {
            let json = """
{
    "odata.metadata": "http://datamall2.mytransport.sg/ltaodataservice/$metadata#BusArrivalv2/@Element",
    "BusStopCode": "20251",
    "Services": [
        {
            "ServiceNo": "176",
            "Operator": "SMRT",
            "NextBus": {
                "OriginCode": "10009",
                "DestinationCode": "45009",
                "EstimatedArrival": "2020-02-12T14:09:11+08:00",
                "Latitude": "1.301219",
                "Longitude": "103.762202",
                "VisitNumber": "1",
                "Load": "SEA",
                "Feature": "WAB",
                "Type": "DD"
            },
            "NextBus2": {
                "OriginCode": "10009",
                "DestinationCode": "45009",
                "EstimatedArrival": "2020-02-12T14:21:19+08:00",
                "Latitude": "1.2731256666666666",
                "Longitude": "103.800273",
                "VisitNumber": "1",
                "Load": "SEA",
                "Feature": "WAB",
                "Type": "DD"
            },
            "NextBus3": {
                "OriginCode": "10009",
                "DestinationCode": "45009",
                "EstimatedArrival": "2020-02-12T14:44:30+08:00",
                "Latitude": "0",
                "Longitude": "0",
                "VisitNumber": "1",
                "Load": "SEA",
                "Feature": "WAB",
                "Type": "DD"
            }
        },
        {
            "ServiceNo": "78",
            "Operator": "TTS",
            "NextBus": {
                "OriginCode": "28009",
                "DestinationCode": "28009",
                "EstimatedArrival": "2020-02-12T14:09:09+08:00",
                "Latitude": "1.3069268333333333",
                "Longitude": "103.73333",
                "VisitNumber": "1",
                "Load": "SEA",
                "Feature": "WAB",
                "Type": "DD"
            },
            "NextBus2": {
                "OriginCode": "28009",
                "DestinationCode": "28009",
                "EstimatedArrival": "2020-02-12T14:26:17+08:00",
                "Latitude": "1.3086495",
                "Longitude": "103.76608433333334",
                "VisitNumber": "1",
                "Load": "SEA",
                "Feature": "WAB",
                "Type": "DD"
            },
            "NextBus3": {
                "OriginCode": "28009",
                "DestinationCode": "28009",
                "EstimatedArrival": "2020-02-12T14:36:38+08:00",
                "Latitude": "1.3126545",
                "Longitude": "103.7666475",
                "VisitNumber": "1",
                "Load": "SEA",
                "Feature": "WAB",
                "Type": "DD"
            }
        }
    ]
}
"""
            // simulated api response
            let data = json.data(using: .utf8)!
            do {
                let decoder = JSONDecoder()
                // decoder.dateDecodingStrategy = .iso8601  // <-- this also works
                decoder.dateDecodingStrategy = .formatted(formatter)
                let decoded = try decoder.decode(BusTimings.self, from: data)
                print("\n---> decoded: \n \(decoded)")
                services = decoded.services
            } catch {
                print("==> decoding error: \(error)")
            }
        }
    }
    
}

struct BusTimings: Codable {
    let busStopCode: String
    let services: [Service]
    
    enum CodingKeys: String, CodingKey {
        case busStopCode = "BusStopCode"
        case services = "Services"
    }
}

// MARK: - Service
struct Service: Codable {
    let serviceNo, serviceOperator: String
    let nextBus, nextBus2, nextBus3: NextBus
    
    enum CodingKeys: String, CodingKey {
        case serviceNo = "ServiceNo"
        case serviceOperator = "Operator"
        case nextBus = "NextBus"
        case nextBus2 = "NextBus2"
        case nextBus3 = "NextBus3"
    }
}

// MARK: - NextBus
struct NextBus: Codable {
    let originCode, destinationCode: String
    let estimatedArrival: Date
    let latitude, longitude, visitNumber, load: String
    let feature, type: String
    
    enum CodingKeys: String, CodingKey {
        case originCode = "OriginCode"
        case destinationCode = "DestinationCode"
        case estimatedArrival = "EstimatedArrival"
        case latitude = "Latitude"
        case longitude = "Longitude"
        case visitNumber = "VisitNumber"
        case load = "Load"
        case feature = "Feature"
        case type = "Type"
    }
}