Search code examples
swiftjsondecoder

How do I bake DateFormatter into a Swift struct so that I don't keep needing to tell JSONDecoder?


I am using Swift 4 to decode some JSON from Twitter:

struct Tweet: Codable {

    let id: String
    let createdAt: Date
    let text: String

    enum CodingKeys: String, CodingKey {
        case id = "id_str"
        case createdAt = "created_at"
        case text
    }
}

let decoder = JSONDecoder()

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "eee MMM dd HH:mm:ss ZZZZ yyyy"
decoder.dateDecodingStrategy = .formatted(dateFormatter)

let tweets = try decoder.decode([Tweet].self, from: data!)

How can I make it so my code doesn't have to keep remembering to set decoder.dateDecodingStrategy. Ideally the Tweet struct would be aware of its date format with a dateFormatter constant static member variable initialised to the correct format.

I imagine I need to use init(decoder: Decoder) somehow on Tweet but I am not sure how.


Solution

  • You can extend Formatter and create a custom static DateFormatter.

    extension Formatter {
        static let custom: DateFormatter = {
            let formatter = DateFormatter()
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.dateFormat = "eee MMM dd HH:mm:ss ZZZZ yyyy"
            return formatter
        }()
    }
    

    And if you would like to make Tweet parse your date string you can provide your own custom decoder initializer as follow:

    extension Tweet {
        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            id = try container.decode(String.self, forKey: .id)
            text = try container.decode(String.self, forKey: .text)
            createdAt = try Formatter.custom.date(from: container.decode(String.self, forKey: .createdAt))!
        }
    }
    

    This assumes that your date string it is properly formatted, if your date string is not guarantee to be properly formatter you can make your Date property optional and remove the force unwrap from the date(from: String) method.