import Foundation 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 }() 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 }() } public protocol FormattedDate: Codable { static var formatter: DateFormatter { get } var date: Date { get } init(date: Date) } extension FormattedDate { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let string = try container.decode(String.self) guard let date = Self.formatter.date(from: string) else { let debug = "Date string does not match format expected by formatter." throw DecodingError.dataCorruptedError(in: container, debugDescription: debug) } self.init(date: date) } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() let string = Self.formatter.string(from: self.date) try container.encode(string) } } struct ISO8601Date: FormattedDate { static let formatter: DateFormatter = .iso8601Full let date: Date } struct YYYMMddDate: FormattedDate { static var formatter: DateFormatter = .yyyyMMdd let date: Date } public struct RSSFeed: Codable { public struct Feed: Codable { public struct Podcast: Codable { public let name: String public let artistName: String public let url: URL private let _releaseDate: YYYMMddDate public var releaseDate: Date { return self._releaseDate.date } private enum CodingKeys: String, CodingKey { case name case artistName case url case _releaseDate = "releaseDate" } } public let title: String public let country: String private let _updated: ISO8601Date public var updated: Date { return self._updated.date } public let podcasts: [Podcast] private enum CodingKeys: String, CodingKey { case title case country case _updated = "updated" case podcasts = "results" } } public let feed: Feed } public typealias Feed = RSSFeed.Feed public typealias Podcast = Feed.Podcast 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" } ] } } """ let data = Data(json.utf8) let decoder = JSONDecoder() 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) }