Guest User

JSON Handling

a guest
Apr 29th, 2020
40
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 4.43 KB | None | 0 0
  1. import Foundation
  2.  
  3. struct Settings {
  4.     static let shared = Settings()
  5.     let apiHost = "[redacted]"
  6.     let apiPath = "[redacted]"
  7. }
  8.  
  9. // MARK: - Common endpoint handling
  10.  
  11. enum Endpoint: String, CaseIterable {
  12.   case locations
  13.   case reportedquests
  14.   // Other endpoints here
  15.  
  16.   var filename: String {
  17.     return self.rawValue
  18.   }
  19.  
  20.   var fullURL: URL {
  21.     return URL(string: "\(Settings.shared.apiHost)\(Settings.shared.apiPath)\(filename)")!
  22.   }
  23. }
  24.  
  25. /// All endpoint responses must conform to this protocol
  26. protocol QTResponse: Decodable {
  27.   associatedtype ResponseType: Codable
  28.   var timestamp: Date { get }
  29.   var error: String? { get }
  30.   var result: ResponseType? { get }
  31. }
  32.  
  33. // MARK: - Endpoint definitions
  34.  
  35. struct LocationsResponse: QTResponse {
  36.   let timestamp: Date
  37.   let error: String?
  38.   let result: [Location]?
  39. }
  40.  
  41. enum LocationType: String, Codable {
  42.   case stop
  43.   case gym
  44. }
  45.  
  46. struct Location: Codable {
  47.   let identifier: String
  48.   let name: String
  49.   let type: LocationType
  50.   let latitude: Double
  51.   let longitude: Double
  52. }
  53.  
  54. struct ReportedQuestsResponse: QTResponse {
  55.   let timestamp: Date
  56.   let error: String?
  57.   let result: [ReportedQuest]?
  58. }
  59.  
  60. struct ReportedQuest: Codable {
  61.   let location: String
  62.   let rewardType: String
  63.   let rewardDetails: String?
  64.   let task: String?
  65.   let reportedBy: String
  66. }
  67.  
  68. // MARK: - Custom JSON decoder
  69.  
  70. class CustomJSONDecoder: JSONDecoder {
  71.  
  72.   enum JSONError: Error {
  73.     case noResultError
  74.     case serverError(text: String)
  75.   }
  76.  
  77.   override init() {
  78.     super.init()
  79.     self.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
  80.   }
  81.  
  82.   override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: QTResponse {
  83.     let decoded = try super.decode(type, from: data)
  84.     guard decoded.error == nil else { throw JSONError.serverError(text: decoded.error!) }
  85.     guard decoded.result != nil else { throw JSONError.noResultError }
  86.     return decoded
  87.   }
  88. }
  89.  
  90. extension DateFormatter {
  91.   static let iso8601Full: DateFormatter = {
  92.     let formatter = DateFormatter()
  93.     formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
  94.     formatter.calendar = Calendar(identifier: .iso8601)
  95.     formatter.timeZone = TimeZone(secondsFromGMT: 0)
  96.     formatter.locale = Locale(identifier: "en_US_POSIX")
  97.     return formatter
  98.   }()
  99. }
  100.  
  101. // MARK: - RequestHelper
  102.  
  103. class RequestHandler {
  104.  
  105.   enum RequestError: Error {
  106.     case notHttpResponse
  107.     case noData
  108.     case parseError
  109.   }
  110.  
  111.   static func get(_ url: URL, callback: @escaping (Error?, Data?, Int?) -> Void) {
  112.     var request = URLRequest(url: url)
  113.     request.httpMethod = "GET"
  114.     // Add some custom headers here
  115.     let task = URLSession.shared.dataTask(with: request) { (data, res, err) in
  116.       DispatchQueue.main.async {
  117.         guard err == nil else { return callback(err, nil, nil) }
  118.         guard let httpResponse = res as? HTTPURLResponse
  119.           else { return callback(RequestHandler.RequestError.notHttpResponse, nil, nil) }
  120.         guard let responseData = data else { return callback(RequestHandler.RequestError.noData, nil, httpResponse.statusCode) }
  121.         callback(nil, responseData, httpResponse.statusCode)
  122.       }
  123.     }
  124.     task.resume()
  125.   }
  126. }
  127.  
  128. // MARK: - DataLoader
  129.  
  130. class DataLoader {
  131.   static let shared = DataLoader()
  132.  
  133.   func loadAll() {
  134.     let decoder = CustomJSONDecoder()
  135.     Endpoint.allCases.forEach { endpoint in
  136.       RequestHandler.get(endpoint.fullURL) { (error, data, statusCode) in
  137.         guard error == nil else { print("Request error: \(error!.localizedDescription)"); return }
  138.         guard let data = data else { print("No data returned from server"); return }
  139.         do {
  140.           switch endpoint {
  141.           case .locations:
  142.             let response = try decoder.decode(LocationsResponse.self, from: data)
  143.             response.result?.forEach { print($0.name) }
  144.           case.reportedquests:
  145.             let response = try decoder.decode(ReportedQuestsResponse.self, from: data)
  146.             response.result?.forEach { print($0.rewardType) }
  147.           }
  148.         } catch CustomJSONDecoder.JSONError.noResultError {
  149.           print("Result object was nil")
  150.         } catch CustomJSONDecoder.JSONError.serverError(let text) {
  151.           print("Server returned error message: \(text)")
  152.         } catch {
  153.           print("JSON decoding error: \(error.localizedDescription)")
  154.         }
  155.       }
  156.     }
  157.   }
  158.  
  159. }
  160.  
  161. DataLoader.shared.loadAll()
Add Comment
Please, Sign In to add comment