Encoding and Decoding in Swift 4
Bài đăng này đã không được cập nhật trong 7 năm
Codable protocol
Swift 4 cung cấp một cách mới để tạo & parse JSON bằng cách sử dụng Codable protocol. Có nhiều cách khác nhau, nơi bạn muốn chuyển đổi 1 class sang Data. Một nhu cầu rất phổ biến là khi bạn muốn POST một dữ liệu kiểu JSON như là một HTTP body. Một trong những cách tiếp cận phổ biến nhất được trình bày dưới đây:
import UIKit
struct Person {
var name: String
var gender: Bool // male: true | female: false
func toDictionary() -> [String: Any] {
return ["name": self.name, "gender": self.gender]
}
}
let person = Person(name: "viva", gender: true)
let data = try? JSONSerialization.data(withJSONObject: person.toDictionary(), options: [])
JSONSerialization làm nhiệm vụ trả về một đối tượng Data, cái mà có thể được truyền qua HTTP Body đến một Request. Codable có thể thay thế NSEncoding trong việc serialize các object để ghi xuống file hoặc đọc ngược lên một cách đơn giản và dễ sử dụng. Nó cũng có thể làm việc với plist dễ dàng như làm việc với JSON, cho phép chúng ta viết các các tùy chỉnh encode và decode theo các định dạng dữ liệu khác nhau. Giả sử ta có 1 JSON như sau:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
Tạo 1 struct tương ứng:
struct Todo: Codable {
var title: String
var id: Int?
var userId: Int
var completed: Int
}
Trước Swift 4, để làm việc với data từ API trả về ta sẽ tốn khá nhiều code để thực hiện:
struct Todo {
// ...
init?(json: [String: Any]) {
guard let title = json["title"] as? String,
let id = json["id"] as? Int,
let userId = json["userId"] as? Int,
let completed = json["completed"] as? Int else {
return nil
}
self.title = title
self.userId = userId
self.completed = completed
self.id = id
}
}
Fetch dữ liệu API trả về:
static func todoByID(_ id: Int, completionHandler: @escaping (Todo?, Error?) -> Void) {
// set up URLRequest with URL
let endpoint = Todo.endpointForID(id)
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not construct URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
// Make request
let session = URLSession.shared
let task = session.dataTask(with: urlRequest, completionHandler: {
(data, response, error) in
// handle response to request
// check for error
guard error == nil else {
completionHandler(nil, error!)
return
}
// make sure we got data in the response
guard let responseData = data else {
print("Error: did not receive data")
let error = BackendError.objectSerialization(reason: "No data in response")
completionHandler(nil, error)
return
}
// parse the result as JSON
// then create a Todo from the JSON
do {
if let todoJSON = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any],
let todo = Todo(json: todoJSON) {
// created a TODO object
completionHandler(todo, nil)
} else {
// couldn't create a todo object from the JSON
let error = BackendError.objectSerialization(reason: "Couldn't create a todo object from the JSON")
completionHandler(nil, error)
}
} catch {
// error trying to convert the data to JSON using JSONSerialization.jsonObject
completionHandler(nil, error)
return
}
})
task.resume()
}
Và đây là với Codable in Swift 4:
struct Todo: Codable {
// ...
}
static func todoByID(_ id: Int, completionHandler: @escaping (Todo?, Error?) -> Void) {
// ...
let task = session.dataTask(with: urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
// ...
}
guard error == nil else {
// ...
}
let decoder = JSONDecoder()
do {
let todo = try decoder.decode(Todo.self, from: responseData)
completionHandler(todo, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
})
task.resume()
}
JSONEncoder và JSONDecoder
Swift 4 cung cấp 2 class gồm: JSONEncoder và JSONDecoder, 2 class có thể dễ dàng chuyển đổi 1 đối tượng sang 1 JSON encoded. Đây là cách để thực hiện:
import UIKit
struct Person: Codable {
var name: String
var gender: Bool // male: true | female: false
}
let person = Person(name: "viva", gender: true)
let encoder = JSONEncoder()
let encoded = try? encoder.encode(person)
Ở ví dụ trên, hay chú ý đến việc Person thực thi Codable protocol, việc này báo cho trình biên dịch hiểu cấu trúc trong Person có khả năng encode và decode. Cũng giống như encoding, decoding được sử dụng khá đơn giản:
let person = Person(name: "viva", gender: true)
let encoder = JSONEncoder()
let encoded = try? encoder.encode(person)
let decoder = JSONDecoder()
guard let data = encoded else {return}
let decoded = try? decoder.decode(Person.self, from: data)
Serializable
Để thuận tiện hơn cho việc sử dụng, chúng ta có thể tạo 1 protocol để wrap việc xử lý data với 2 class JSONEncoder:
import UIKit
struct Person: Serializable {
var name: String
var gender: Bool // male: true | female: false
}
protocol Serializable: Codable {
func serialize() -> Data?
}
extension Serializable {
func serialize() -> Data? {
let encoder = JSONEncoder()
return try? encoder.encode(self)
}
}
let person = Person(name: "viva", gender: true)
let data = person.serialize()
Conclusion
Qua bài giới thiệu trên chúng ta có thể thấy được việc thao tác với JSON trên Swift 4 đã trở lên khá dễ dàng, loại bỏ được việc sử dụng các thư viện thứ 3 để encode và decode data khi làm việc với API. Tìm hiểu thêm tại đây: https://developer.apple.com/documentation/swift/codable http://media.aboutobjects.com/blog/encoding-and-decoding-in-swift-4
All rights reserved