When Swift 4 went out and we finally had a chance to play with it in a playground, I began reading this article on Ray Wenderlich’s site :
https://www.raywenderlich.com/163857/whats-new-swift-4
I was particularly interested in this part about the new Codable protocol :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// MARK: - Codable struct CuriosityLog: Codable { enum Discovery: String, Codable { case rock, water, martian } var sol: Int var discoveries: [Discovery] } // Create a log entry for Mars sol 42 let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .water, .rock, .rock]) let jsonEncoder = JSONEncoder() // One currently available encoder // Encode the data let jsonData = try jsonEncoder.encode(logSol42) // Create a string from the data let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","water","rock","rock"]}" let jsonDecoder = JSONDecoder() // Pair decoder to JSONEncoder // Attempt to decode the data to a CuriosityLog object let jsonBackData = (jsonString!.data(using: .utf8))! let decodedLog = try jsonDecoder.decode(CuriosityLog.self, from: jsonBackData) decodedLog.sol // 42 decodedLog.discoveries // [rock, water, rock, rock] |
As you can see, it’s a pretty interesting (and simple) way to convert a Codable into a String (a stringified version of a JSON dictionary).
I also read this article from Natasha-The-Robot’s weekly newsletter : https://cur.at/hxKamEa?m=email&sid=QSej7qn
This is interesting: the author creates a new protocol (Serializable) to let the Codable-conforming object encode itself to Data.
1 2 3 4 5 6 7 8 9 10 |
protocol Serializable: Codable { func serialize() -> Data? } extension Serializable { func serialize() -> Data? { let encoder = JSONEncoder() return try? encoder.encode(self) } } |
Well, as far as I’m concerned, I often have to parse a JSON dictionary (a.k.a. a Dictionary<String, Any>), or create a JSON dictionary from an object, not a String or a Data.
So, this is my implementation of this Serializable protocol idea :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
protocol JSONCodable: Codable { typealias JSON = [String : Any] func toDictionary() -> JSON? init?(json: JSON) } extension JSONCodable { func toDictionary() -> JSON? { // Encode the data if let jsonData = try? JSONEncoder().encode(self), // Create a dictionary from the data let dict = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? JSON { return dict } return nil } init?(json: JSON) { do { let data = try JSONSerialization.data(withJSONObject: json, options: []) let object = try JSONDecoder().decode(Self.self, from: data) self = object } catch { return nil } } } struct CassiniLog: JSONCodable { enum Discovery: String, Codable { case rock, water, martian } var sol: Int var discoveries: [Discovery] } let cassiniLogSol42 = CassiniLog(sol: 42, discoveries: [.rock, .rock, .water, .rock]) let json = cassiniLogSol42.toDictionary() // { // "discoveries": [ // "rock", // "rock", // "water", // "rock" // ], // "sol": 42 // } let log = CassiniLog(json: json!) log?.sol // 42 log?.discoveries // [rock, water, rock, rock] |
The JSONCodable protocol now has two required methods :
- a function allowing the Serializable-conforming object to convert itself a JSON dictionary
- an initializer allowing us to initialize a new object by parsing a JSON dictionary.
If you often have to work with web services (as I do), this is handy.