import Foundation // MARK: Extension on Formatter to provide ISO8601DateFormatter instances extension Formatter { static let iso8601: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() // This options is the default (can be omitted but but I prefer to make it explicit) formatter.formatOptions = [.withFractionalSeconds] return formatter }() static let iso8601withFractionalSeconds: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] return formatter }() } // MARK: Custom date decoding strategy extension JSONDecoder.DateDecodingStrategy { static let customISO8601Cascade = custom { let container = try $0.singleValueContainer() let string = try container.decode(String.self) if let date = Formatter.iso8601withFractionalSeconds.date(from: string) ?? Formatter.iso8601.date(from: string) { return date } throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)") } } // MARK: Data models struct Event: Decodable { let id: Int let timestamp: Date } struct EventsResponse: Decodable { let events: [Event] } // MARK: Usage example let jsonData = """ { "events": [ { "id": 1, "timestamp": "2025-09-05T13:25:41Z" }, { "id": 2, "timestamp": "2025-09-01T09:53:38.000Z" }, { "id": 3, "timestamp": "2025-09-18T21:54:22.923456Z" } ] } """.data(using: .utf8)! do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .customISO8601Cascade let response = try decoder.decode(EventsResponse.self, from: jsonData) response.events.forEach { print("\($0.id): \($0.timestamp)") } } catch { print("Decoding failed:", error) }