DateFormatter从JSON数据中解码Swift中的日期错误



这是我得到的错误

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))

当我试图将SwiftUI视图中的estimatedArrival打印为从日期格式CCD_ 1转换而来的Text((时;yyyy-MM-dd’T'HH:MM:ssZ";总体安排

然而,当我更改为estimatedArrival: String而不是Date时,它不再是错误。

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

感谢您通读这篇文章!我感谢你的帮助!

尝试这个解码器(在loadData((中(或它的一些变体,将您的String日期解码为真正的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)")
}

您所在的位置:

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

您的日期格式"yyyy-MM-dd'T'HH:mm:ssZ"看起来像iso8601,所以你也可以试试:

decoder.dateDecodingStrategy = .iso8601

第1版:显示我答案的测试代码与官方数据一致。

来自官方网站:https://datamall.lta.gov.sg/content/datamall/en/dynamic-data.html使用Bus Arrival数据,下载为json并在此作为示例显示。

下面的测试代码显示

decoder.dateDecodingStrategy = .iso8601

decoder.dateDecodingStrategy = .formatted(formatter)

可以对来自官方数据集的CCD_ 9日期进行解码。

使用

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

同样有效。

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