Search code examples
iosswiftdateutcjsondecoder

issue with parsing dateUTC swift from spaceX api v5?


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/ enter image description here

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

Solution

  • 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.