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!
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"
}
}