Swift Codable With Custom Dates
Làm cách nào để bạn decode một file JSON có nhiều định dạng ngày khác nhau? Nếu bạn may mắn bằng cách sử dụng một dateDecodingStrategy có thể là đủ. Thật không may, nó có hỗ trợ giới hạn cho định dạng .iso8601 và bạn chỉ có thể thiết lập một giải pháp tại một thời điểm vì vậy nó không giúp ...
Làm cách nào để bạn decode một file JSON có nhiều định dạng ngày khác nhau? Nếu bạn may mắn bằng cách sử dụng một dateDecodingStrategy có thể là đủ. Thật không may, nó có hỗ trợ giới hạn cho định dạng .iso8601 và bạn chỉ có thể thiết lập một giải pháp tại một thời điểm vì vậy nó không giúp đỡ khi bạn có hai hoặc nhiều định dạng ngày khác nhau. Vậy chúng ta phải xử lý các định dạng khác nhau trong cùng một file JSON như thế nào, hôm nay tôi sẽ giới thiệu các bạn một giải pháp mà không cần phải sử dụng library hỗ trợ bên ngoài, mà nó sẽ được hỗ trợ trên swift 4.
Ở ví dụ của chúng ta sử dụng file JSON như sau:
// 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" } ] } } """
Chú ý rằng ở đây có ngày 2 định dạng ngày khác nhau. Đầu tiên là một timestamp sử dụng cho lastupdated. Đây là định dạng quen thuộc iso8601:
"updated":"2017-11-16T02:02:55.000-08:00",
Thứ hai là release date format đơn giản yyyy-mm-dd
"releaseDate":"2017-11-12",
Swift 4 mang lại cho chúng ta một cách chuẩn hóa để encode / decode JSON sử dụng các loại tùy chỉnh của riêng chúng bằng cách sử dụng giao thức Codable. Các kiểu như String, URL và Date đã được Codable để chúng ta có thể sử dụng chúng để xây dựng các loại Codable cho podcast và feed. Chúng ta cũng có thể tổ chức các cấu trúc Swift để mô hình trực tiếp cấu trúc RSS Feed:
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
Để encode chuỗi JSON của chúng ta, chúng ta chuyển nó sang Data và nạp nó vào một JSONDecoder:
let data = Data(json.utf8) let decoder = JSONDecoder() let rssFeed = try! decoder.decode(RSSFeed.self, from: data)
Thật không may khi ở đây chúng ta có 2 định dạng ngày.
Bạn có thể thay đổi cách decode JSON xử lý ngày bằng cách sử dụng date decoding strategy. updated Feed có định dạng ngày tháng iso8601 được hỗ trợ nên chúng tôi có thể thử rằng:
let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601
Chúng ta có thể sử dụng cách trên, tuy nhiên decodeingStategy của Foundation lại không hỗ trợ đến phần tử nhỏ của giây (02:55.000). Chúng ta có thể custome để có định dạng full format như sau:
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 }() }
Chú ý rằng .SSS trong date format, để sử dụng custom data formater khi decode JSON data:
let data = Data(json.utf8) let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full) let rssFeed = try! decoder.decode(RSSFeed.self, from: data)
Sử dụng dateDecodingStrategy tốt khi JSON data của bạn chỉ có một date format. Hãy cùng xem chúng ta decode Postcast xử lỹ releaseDate format (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 }() }
Bây giờ chúng ta kế thừa required initializer init(from: Decoder) để xử lý custom 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.") } } }
Để decode full feed, chúng ta thực hiện như sau:
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
Bạn có thể lấy đoạn mã và dữ liệu mẫu JSON từ GitHub Gist này nếu bạn muốn thử nó trong playground. Nguồn