###### tags: `第14屆IT邦鐵人賽文章` # 【在 iOS 開發路上的大小事2-Day03】工人智慧!JSON 轉起來 最近專案需要有轉換 JSON 的功能,所以就去研究一下了 Swift 原生就有 JSON 編碼、解碼的功能 只要讓 class/struct 繼承 Encodable 或是 Decodable 就可以有編碼 或是 解碼的功能 如果是繼承 Codable 的話,則是 Encodable 跟 Decodable 這兩種功能都有 ![](https://i.imgur.com/grtfKFg.png) ▲圖截自 Xcode 內的 Swift.Misc 那接下來就來開始製作要做成 JSON 的 class ```swift // 先寫沒繼承 Codable 的 class class Event: NSObject { var Index: Int? var TimeStamp: Int64? var EventID: Int? var EventValue: Int? var EventAttribute: [String]? } ``` ↑ 分別寫兩個來做個對比 ↓ ```swift // 在寫有繼承 Codable 的 class class Event: NSObject, Codable { var Index: Int? var TimeStamp: Int64? var EventID: Int? var EventValue: Int? var EventAttribute: [String]? } ``` 接著先來寫一下要製作成 JSON 的內容 ```swift let event = Event() event.Index = 0 event.TimeStamp = 1648647707 event.EventID = 2 event.EventValue = 0 event.EventAttribute = ["饅頭", "1", "早餐早餐"] ``` 接著再來寫轉換 func~ ```swift /// 轉換 API 上傳的 JSON Format /// - Parameters: /// - input: EventTable /// - Returns: JSON Data func GetJsonEventReport(input: Event) -> Data? { } ``` 這邊有兩種版本的寫法,一種是對 Encodable 進行 extension,一種是直接寫 ```swift // Version 1:對 Encodable 進行 extension extension Encodable { func asDictionary() throws -> [String: Any] { let data = try JSONEncoder().encode(self) guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) as? [String: Any] else { throw NSError() } return dictionary } } func GetJsonEventReport(input: Event) -> Data? { let dic1 = try? input.asDictionary() print("dic1:\n \(dic1)\n") let jsonResults = try? JSONSerialization.data(withJSONObject: dic1 ?? [:], options: .prettyPrinted) return jsonResults } ``` ``` Version 1 輸出結果: dic1: Optional(["EventID": 2, "EventValue": 0, "Index": 0, "TimeStamp": 1648647707, "EventAttribute": <__NSArrayI 0x600002036af0>( 饅頭, 1, 早餐早餐 ) ]) ``` ```swift // Version 2:直接寫 func GetJsonEventReport(input: Event) -> Data? { let dic2: [String: Any] = [ "CmdType": 2, "ID": input.Index, "DeviceID": "SK0xE0512444378B", "UserID": "leo160918@gmail.com", "EventID": input.EventID, "EventValue": input.EventValue, "EventRecordTime": input.TimeStamp?.timeStampToDate().convert2UtcStr(), "EventAttribute": input.EventAttribute, "Note": input.EventAttribute?.last ] print("dic2:\n \(dic2)\n") let jsonResults = try? JSONSerialization.data(withJSONObject: dic2 ?? [:], options: .prettyPrinted) return jsonResults } ``` ``` Version 2 輸出結果: dic2: ["CmdType": 2, "EventRecordTime": Optional("30/03/2022T13:41:47"), "ID": Optional(0), "EventValue": Optional(0), "DeviceID": "SK0xE0512444378B", "Note": Optional("早餐早餐"), "EventID": Optional(2), "EventAttribute": Optional(["饅頭", "1", "早餐早餐"])] ``` 這兩種版本的寫法都各有優缺點~ 下面是我想到的啦,可能還有其他的? ``` Version 1 優點: ● 可以不用手打,用 Code 直接生成 Dictionary ● 承上點,不會有打錯 Key 的問題 Version 1 缺點: ● 彈性比較低 ● 承上點,如果有要新增其他 Key 的話,要手動新增 ● 承上點,如果要將自動生成的 Dictionary 裡面特定的 Key 更換的話,要先將原先的 Key 移除 Version 2 優點: ● 彈性比較高 ● 承上點,如果有要新增其他 Key 的話,可以直接新增 Version 2 缺點: ● 用手打 Key,可能會有打錯 Key 的問題 ● Code 不夠簡潔,要逐一給值 ``` 然後補充一下 JSONSerialization 這個東東 ![](https://i.imgur.com/yxLOVDt.png) ▲圖截自 Apple Developer Documentation 一般來說只有 NSArray、NSDictionary 這兩種資料結構可以轉成 JSON 但如果設成 **fragmentsAllowed** (前身是 **allowFragments**) 的話,就允許將 String、Number、null、Bool 轉成 JSON 像是下面這樣 ↓ ```swift guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) as? [String: Any] else { throw NSError() } ``` 另外將輸出格式設為 **prettyPrinted** 是為了美化格式 像是下面這樣 ↓ ```swift let jsonResults = try? JSONSerialization.data(withJSONObject: dic2 ?? [:], options: .prettyPrinted) ``` 如果要看輸出的 JSON 檔的話,可以透過下面的方法 ```swift // jsonResults 是透過 Version 2 的 func GetJsonEventReport(input: Event) -> Data? 回傳出來的 Data if let jsonData = jsonResults, let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { let pathWithFileName = documentDirectory.appendingPathComponent("Event_JSON") do { try jsonData.write(to: pathWithFileName) } catch { // handle error } } ``` 輸出結果如下~ ```json { "EventRecordTime" : "30\/03\/2022T13:41:47", "ID" : 0, "DeviceID" : "SK0xE0512444378B", "EventValue" : 0, "EventAttribute" : [ "饅頭", "1", "早餐早餐" ], "Note" : "早餐早餐", "EventID" : 2, "CmdType" : 2 } ``` ## 參考資料 > 1. https://yongpenglovemimi123.gitbook.io/henry/ios/jsonserialization.readingoptions > 2. https://yongpenglovemimi123.gitbook.io/henry/ios/jsonserialization.writingoptions > 3. https://jjeremy-xue.medium.com/swift-%E7%96%91%E5%95%8F-jsonserialization-%E7%9A%84-options-d151a1e66266 > 4. https://www.codeleading.com/article/5134462945/ > 5. https://developer.apple.com/documentation/foundation/jsonserialization > 6. https://ithelp.ithome.com.tw/articles/10245672