Viblo Code
0

Encoding and Decoding in Swift 4

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

Viblo
Let's register a Viblo Account to get more interesting posts.