Search code examples
swiftgeojsondecodingiso8601

Can MKGeoJSONDecoder be extended to parse ISO8601 dates like JSONDecoder?


I have GeoJSON data from an API and it contains dates that are in ISO8601 format. I can decode them in SwiftUI as a string and manipulate them through a calculated field to get a version that is type Date but it's clumsy. I know the JSONDecoder supports date en/decoding options and I'd like the same behaviour.. similar to this:

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

I was thinking of maybe an extension to MKGeoJSONDecoder but I can't figure out how to even get started because of the need to be in the parsing flow.

Thoughts? Thanks


Solution

  • Thanks vadian, all of the data I wanted was in the properties attribute so there was really no value in using MKGeoJSONDecoder. I just switched to normal JSONDecoder and put a custom ISO8601 formatter to match my data and all works great.

    In case anyone wants to reference, here is the playground code. You'd want to add a bunch of error checking of course. And thanks to Sarunw for the great info on the custom date decoding (https://sarunw.com/posts/how-to-parse-iso8601-date-in-swift/)

    import Foundation
    import MapKit
    
    struct Response: Codable {
        let features: [Feature]
    }
    
    struct Feature: Codable {
        let properties: Properties
    }
    
    struct Properties: Codable {
        let stnNamValue: String?
        let dateTmValue: Date?
        
        enum CodingKeys: String, CodingKey {
            case stnNamValue = "stn_nam-value"
            case dateTmValue = "date_tm-value"
        }
    }
    
    Task {
        // get the data from API
        let url = URL(string: "https://api.weather.gc.ca/collections/swob-realtime/items?f=json&sortby=-date_tm-value&clim_id-value=5050919&limit=3")
        let (data, _) = try await URLSession.shared.data(from: url!)
        
        // create the decoder with custom ISO8601 formatter
        let decoder = JSONDecoder()
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withFullDate, .withFullTime,.withFractionalSeconds, .withTimeZone]
        decoder.dateDecodingStrategy = .custom({ decoder in
            let container = try decoder.singleValueContainer()
            let dateString = try container.decode(String.self)
            if let date = formatter.date(from: dateString) {return date}
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)")
        })
        
        // decode & print the results
        let decodedResponse = try decoder.decode(Response.self, from: data)
        decodedResponse.features.forEach {(feature) in
            print("Station: \(feature.properties.stnNamValue ?? "<no value>"), Date: \(feature.properties.dateTmValue)")
        }
    }
    

    Output is the date/times of the most recent surface weather observations for Flin Flon, Manitoba:

    Station: FLIN FLON, Date: Optional(2022-02-16 12:22:00 +0000)
    Station: FLIN FLON, Date: Optional(2022-02-16 12:21:00 +0000)
    Station: FLIN FLON, Date: Optional(2022-02-16 12:20:00 +0000)