Sử dụng Codable để parse các Date format khác nhau trong Swift



  • Giả dụ chúng ta đang có một dữ liệu JSON trả về chứa nhiều định dạng ngày tháng (date format), thì bạn sẽ decode JSON đó như thế nào?. Đáp án đầu tiên đó là sử dụng dateDecodingStrategy, tuy nhiên nó chỉ hỗ trợ ở mức hạn chế cho định dạng .iso8601 và chúng ta cũng chỉ được set một strategy tại cùng thời điểm, vì vậy bài viết này sẽ giới thiệu cách sử dụng Codeable để xử lý bài toàn JSON có nhiều định dạng Date khác nhau. 

    Ở đây tôi sử dụng iTunes RSS Feed Generator để làm ví dụ mẫu. Phía dưới là dữ liệu JSON trả về top 3 podcast:

    // https://rss.itunes.apple.com/api/v1/gb/podcasts/top-podcasts/all/3/explicit.json
    let json = """
    {
      "feed": {
        "title":"Top Audio Podcasts",
        "country":"gb",
        "updated":"2017-11-16T02:02:55.000-08:00",
        "results":[
          {
          "artistName":"BBC Radio",
          "name":"Blue Planet II: The Podcast",
          "releaseDate":"2017-11-12",
          "url":"https://itunes.apple.com/gb/podcast/blue-planet-ii-the-podcast/id1296222557?mt=2"
        },
        {
          "artistName":"Audible",
          "name":"The Butterfly Effect with Jon Ronson",
          "releaseDate":"2017-11-03",
          "url":"https://itunes.apple.com/gb/podcast/the-butterfly-effect-with-jon-ronson/id1258779354?mt=2"
        },
        {
          "artistName":"TED",
          "name":"TED Talks Daily",
          "releaseDate":"2017-11-16",
          "url":"https://itunes.apple.com/gb/podcast/ted-talks-daily/id160904630?mt=2"
        }
        ]
      }
    }
    """

    Ở đây chúng ta có hai kiểu date format, đầu tiên là kiểu iso8601 ở trường "updated":

     

    "updated":"2017-11-16T02:02:55.000-08:00",
    

     

    Tiếp đến là format yyyy-MM-dd ở trường "releaseDate"

     

    "releaseDate":"2017-11-12",
    

     

    Swift Codable

    Swift 4 cung cấp cho chúng ta một công cụ rất tiện lợi cho việc encode/decode JSON, đó là protocol Codable. Các kiểu phổ biến như String, URL và Date đều đã adopt protocol này, do đó chúng ta có thể build một kiểu Codabletype cho dữ liệu podcast và feed ở khối JSON trên.

    import Foundation
    
    struct RSSFeed: Codable {
      struct Feed: Codable {
        struct Podcast: Codable {
          let name: String
          let artistName: String
          let url: URL
          let releaseDate: Date
        }
    
        let title: String
        let country: String
        let updated: Date
        let podcasts: [Podcast]
    
        private enum CodingKeys: String, CodingKey {
          case title
          case country
          case updated
          case podcasts = "results"
        }
      }
    
      let feed: Feed
    }
    
    typealias Feed = RSSFeed.Feed
    typealias Podcast = Feed.Podcast
    

     

    Để decode JSON thì chúng ta sẽ convernt nó thành kiểu Data và đưa vào trong JSONDecoder:

     

    let data = Data(json.utf8)
    let decoder = JSONDecoder()
    let rssFeed = try! decoder.decode(RSSFeed.self, from: data)

     

    Và hiển nhiên là chúng ta sẽ gặp lỗi không thể decode hai kiểu Date. Do đó bạn cần phải thay đổi cashc mà JSON decode xử lý kiểu Date bằng cách thiết lập date decoding strategy như sau:

     

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

     

    Không may là nó vẫn không chạy được, có vẻ như là thư viên Foundation không hỗ trợ xử lý định dạng iso8601 cho đến mili giây. Tuy nhiên đó không phải là vấn đề lớn, chúng ta có thể tự tạo ra một custom date formatter để xử lý toàn bộ format trên:

     

    extension DateFormatter {
      static let iso8601Full: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
      }()
    }

     

    Note the .SSS in the date format. To use this custom data formatter when decoding our JSON data:

     

    let data = Data(json.utf8)
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
    let rssFeed = try! decoder.decode(RSSFeed.self, from: data)

    Như vậy là chúng ta đã xử lý xong trường "updated", tiếp theo là đến trường "releaseDate". Đầu tiên chúng ta cần một tạo một date formatter để xử lý định dạng yyyy-MM-dd:

     

    extension DateFormatter {    
      static let yyyyMMdd: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
      }()
    }

    Tiếp đến là extend Podcast với hàm init(from: Decoder) có vai trò xử lý date format:

     

    extension Podcast {
      init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        artistName = try container.decode(String.self, forKey: .artistName)
        url = try container.decode(URL.self, forKey: .url)
    
        let dateString = try container.decode(String.self, forKey: .releaseDate)
        let formatter = DateFormatter.yyyyMMdd
        if let date = formatter.date(from: dateString) {
            releaseDate = date
        } else {
            throw DecodingError.dataCorruptedError(forKey: .releaseDate,
                  in: container,
                  debugDescription: "Date string does not match format expected by formatter.")
        }
      }
    }

    Đoạn code trên decode một dictionary chứa key cho từng property trong struct Podcast bằng cách sử dụng enum CodingKeys. Đầu tiên chúng ta thử decode trường .releaseDate thành một chuỗi String và sử dụng data formatter để parse thành kiểu Date, hoặc nếu có lỗi thì sẽ throw ra case .dataCorruptedError. Ghép tât cả lại thì chúng ta đã có thể decode toàn bộ khối JSON rồi.

     

    let data = Data(json.utf8)
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
    let rssFeed = try! decoder.decode(RSSFeed.self, from: data)
    
    let feed = rssFeed.feed
    print(feed.title, feed.country, feed.updated)
    
    feed.podcasts.forEach {
      print($0.name)
    }
    
    Top Audio Podcasts gb 2017-11-16 10:02:55 +0000
    Blue Planet II: The Podcast
    The Butterfly Effect with Jon Ronson
    TED Talks Daily

     


Hãy đăng nhập để trả lời
 

Có vẻ như bạn đã mất kết nối tới LaptrinhX, vui lòng đợi một lúc để chúng tôi thử kết nối lại.