I have an issue with decoding dateUTC in swift. It shows nil every time I run the app. the API has a date, but the JSONDecoder is not working. I have posted an image JSON response and Service.swift
JSON from spaceX API v5
https://api.spacexdata.com/v5/launches/
final class Service {
private let bassURL: String = "https://api.spacexdata.com/v5/"
private lazy var decoder:JSONDecoder = {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-mm-dd"
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
return jsonDecoder
}()
public static let standard: Service = Service()
func get<T: Decodable>(path: endpoint, responseType:T.Type) -> AnyPublisher<T,ServiceError>{
AF.request(bassURL + path.rawValue, method: .get)
.publishDecodable(type: responseType,decoder: self.decoder)
.value()
.mapError(ServiceError.init(error:))
.eraseToAnyPublisher()
}
}
update: still have some issue with tired this
private lazy var decoder:JSONDecoder = {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
jsonDecoder.dateDecodingStrategy = .custom{ decoder -> Date in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
let dateFormatter = DateFormatter()
if dateString.range(of: #":\d{2}[+-]"#, options: .regularExpression) != nil {
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
} else {
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ"
}
guard let date = dateFormatter.date(from: dateString) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date") }
return date
}
return jsonDecoder
}()
Launch
// MARK: - Launch
struct Launch: Codable {
let fairings: Fairings?
let links: Links
let staticFireDateUTC: String?
let staticFireDateUnix: Int?
let net: Bool
let window: Int?
let rocket: Rocket
let success: Bool?
let failures: [Failure]
let details: String?
let crew: [Crew]
let ships, capsules, payloads: [String]
let launchpad: Launchpad
let flightNumber: Int?
let name: String?
let dateUTC: Date?
let dateUnix: Int?
let dateLocal: Date?
let datePrecision: DatePrecision?
let upcoming: Bool
let cores: [Core]
let autoUpdate, tbd: Bool?
let launchLibraryID: String?
let id: String
enum CodingKeys: String, CodingKey {
case fairings, links
case staticFireDateUTC = "static_fire_date_utc"
case staticFireDateUnix = "static_fire_date_unix"
case net, window, rocket, success, failures, details, crew, ships, capsules, payloads, launchpad
case flightNumber = "flight_number"
case name
case dateUTC = "date_utc"
case dateUnix = "date_unix"
case dateLocal = "date_local"
case datePrecision = "date_precision"
case upcoming, cores
case autoUpdate = "auto_update"
case tbd
case launchLibraryID = "launch_library_id"
case id
}
}
You cannot decode the dates with one fixed format – even if it was valid for one of the strings – because there are two different date strings in the JSON.
Fortunately there is a custom
date decoding strategy.
Replace the lines
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-mm-dd"
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
with
jsonDecoder.dateDecodingStrategy = .custom{ decoder -> Date in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
let dateFormatter = DateFormatter()
if dateString.range(of: #":\d{2}[+-]"#, options: .regularExpression) != nil {
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
} else {
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ"
}
guard let date = dateFormatter.date(from: dateString) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date") }
return date
}
If checks if the string contains a colon followed by two digits and and plus or minus sign and decodes the date conditionally.