Chapter 22: Encoding and Decoding Types

大綱

Encodable and Decodable protocols

  • Swift中encode和decodable的protocols

// encode
func encode(to: Encoder) throws

// decodable
init(from decoder: Decoder) throws

What is Codable?

  • Codable = Encodable + Decodable

typealias Codable = Encodable & Decodable

Automatic encoding and decoding

  • 基本上, 所有在swift standard library的type都是Codable. ex. Int, Array, String, Date…

  • 任何Foundation framework中的type也是Codable。

  • 如果想要自己custom type也是Codable, 只要這個type中所有stored properties也是Codable, 那麼這個type也自動是Codable。

// Employee要是Codable
struct Employee: Codable {
  var name: String
  var id: Int
  // Toy一定也要是Codable
  var favoriteToy: Toy?
}

struct Toy: Codable {
  var name: String
}

JSONEncoder and JSONDecoder

  • 一旦type是Codable, 就可以利用JSONEncoder, 將資料轉成data。

“By design, it’s specified at compilation time as it prevents a security vulnerability where someone on the outside might try to inject a type you weren’t expecting. It also plays well with Swift’s natural preference for static types.”

let toy1 = Toy(name: "Teddy Bear");
let employee1 = Employee(name: "John Appleseed", id: 7, favoriteToy: toy1)

let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(employee1)

let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
// {"name":"John Appleseed","id":7,"favoriteToy":{"name":"Teddy Bear"}}

let jsonDecoder = JSONDecoder()
let employee2 = try jsonDecoder.decode(Employee.self, from: jsonData)

CodingKey protocol and CodingKeys enum

  • 利用CodingKey進行key的轉換

struct Employee: Codable {
  var name: String
  var id: Int
  var favoriteToy: Toy?

  // 1. 宣告一個nested enum type
  // 2. confirm CodingKey
  // 3. 設定raw type, 不是String就是Int
  enum CodingKeys: String, CodingKey {
    // 4. 必須把所有case都寫上,即使不需要進行轉換的key
    case id = "employeeId"
    case name
    case favoriteToy
  }
}

Manual encoding and decoding

  • 如果想要json進行格式轉換(非只是key轉換)

{ "employeeId": 7, "name": "John Appleseed", "favoriteToy": {"name": "Teddy Bear"}}

{ "employeeId": 7, "name": "John Appleseed", "gift": "Teddy Bear" }
struct Employee {
  var name: String
  var id: Int
  var favoriteToy: Toy?

  enum CodingKeys: String, CodingKey {
    case id = "employeeId"
    case name
    case gift
  }
}

// 手動改寫Encodable
extension Employee: Encodable {
  func encode(to encoder: Encoder) throws {
    // 取得encoder的container
    var container = encoder.container(keyedBy: CodingKeys.self)
    // 重新encode
    try container.encode(name, forKey: .name)
    try container.encode(id, forKey: .id)
    // 將favoriteToy?.name取出來,直接mapping到新的key
    try container.encode(favoriteToy?.name, forKey: .gift)
  }
}

// 手動改寫Decodable
extension Employee: Decodable {
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    name = try values.decode(String.self, forKey: .name)
    id = try values.decode(Int.self, forKey: .id)
    // 反向處理
    if let gift = try values.decode(String.self, forKey: .gift) {
      favoriteToy = Toy(name: gift)
    }
  }
}

encodeIfPresent and decodeIfPresent

  • 如果當employees沒有 favorite toy就會導致轉成git產生null value。

{"name":"John Appleseed","gift":null,"employeeId":7}
extension Employee: Encodable {
  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(name, forKey: .name)
    try container.encode(id, forKey: .id)
    // 利用encodeIfPresent, 當employees沒有favorite toy就不會產生gift key
    try container.encodeIfPresent(favoriteToy?.name, forKey: .gift)
  }
}

extension Employee: Decodable {
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    name = try values.decode(String.self, forKey: .name)
    id = try values.decode(Int.self, forKey: .id)
    // 利用decodeIfPresent, 當有gift key才轉換成當employees的favorite toy名字
    if let gift = try values.decodeIfPresent(String.self, forKey: .gift) {
      favoriteToy = Toy(name: gift)
    }
  }

Key points

  • You need to encode (or serialize) an instance before you can save it to a file or send it over the web.

  • You need to decode (or deserialize) to bring it back from a file or the web as an instance.

  • Your type should conform to the Codable protocol to support encoding and decoding.

  • If all stored properties of your type are Codable, then the compiler can automatically implement the requirements of Codable for you.

  • JSON is the most common encoding in modern applications and web services, and you can use JSONEncoder and JSONDecoder to encode and decode your types to and from JSON.

  • Codable is very flexible and can be customized to handle almost any valid JSON.

  • Codable can be used with serialization formats beyond JSON.

Last updated

Was this helpful?