-1

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.

Dan
  • 5,013
  • 5
  • 33
  • 59
  • Maybe with subclassing `JSONDecoder` with a `JSONTweetDecoder` where in its `init` it will set its `decodingStrategy`? – Larme Apr 28 '18 at 11:05
  • In the struct there are two options: Write a custom `init(decoder: Decoder)` initializer or decode the key as `String` and add a computed or lazy instantiated property which does the conversion to `Date` – vadian Apr 28 '18 at 11:16

2 Answers2

1

As suggested by @Larme in comments, you can subclass JSONDecoder and override its init method where you set the dateDecodingStrategy to Twitter's date format. You should also make sure that you set the locale of the DateFormatter correctly, otherwise it won't be able to decode the day/month names correctly. I assume these are in English, so I'd suggest using the Locale en_US_POSIX for your hardcoded date format.

class JSONTweetDecoder: JSONDecoder {
    private static let dateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "eee MMM dd HH:mm:ss ZZZZ yyyy"
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        return dateFormatter
    }()

    override init() {
        super.init()
        self.dateDecodingStrategy = .formatted(JSONTweetDecoder.dateFormatter)
    }
}

Then you just simply need to initialize a JSONTweetDecoder instead of a JSONDecoder when decoding the response.

let tweets = try JSONTweetDecoder().decode([Tweet].self, from: data!)
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
0

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.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571