Encoding and Decoding in Swift筆記

原文:https://www.raywenderlich.com/3418439-encoding-and-decoding-in-swift

基礎(chǔ)語法

Swift將 encoding和 decoding能力內(nèi)置在了系統(tǒng)庫(Codable協(xié)議)中,不需要像OC一樣編寫從JSON String 到 對象的decoding方法(及其逆方法),我們所需要做的只有:

  • 遵守Codable協(xié)議
struct Toy: Codable {
  var name: String
}
struct Employee: Codable {
  var name: String
  var id: Int
  var favoriteToy: Toy
}
  • 聲明encode & 1. decoder
let encoder = JSONEncoder()
let decoder = JSONDecoder()

  • 執(zhí)行編碼和解碼
let toy = Toy(name: "Teddy Bear")
let employee = Employee(name: "John Appleseed", id: 7, favoriteToy: toy)
let data = try encoder.encode(employee)
let string = String(data: data,  encoding: .utf8)
let sameEmployee = try decoder.decode(Employee.self, from: data)

Codable協(xié)議的能力

嵌套編碼

一段用utf8編碼過得JSON下發(fā)到客戶端,實(shí)際上就相當(dāng)于上述代碼中的string,格式化處理后相當(dāng)于:

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

對于favoriteToy字段下嵌套的可以對應(yīng)到Toy的JSON結(jié)構(gòu),由于Toy類型也遵守了Codable協(xié)議,也會被正確轉(zhuǎn)為Toy對象

Snake Case 和 Camel Case之間的轉(zhuǎn)換

可以幫助解決,客戶端常用Camel Case 但 API可能使用Snake Case的問題:

  encoder.keyEncodeingStrategy = .convertToSnakeCase
  decoder.keyDecodingStratefgy = .convertFromSnakeCase

猜測內(nèi)部會對key先做一層轉(zhuǎn)換再去encode/decode

自定義JSON Key

可能出現(xiàn)客戶端和API對key的定義不一樣的情況,如API將favoriteToy改成了gift,客戶端仍想保留變量名為favoriteToy,客戶端可以選擇自定義key值:

struct Employee: Codable {
  var name: String
  var id: Int
  var favoriteToy: Toy
  enum CodingKeys: String, CodingKey {
    case name, id, favoriteToy = "gift"
  }
}

需要注意的是,只有被列入CodingKeys枚舉的變量才會被解析/編碼,所以即使不需要自定義,需要的變量名仍要寫進(jìn)去

處理與客戶端不同的JSON數(shù)據(jù)結(jié)構(gòu)

Flat JSON

假如API把JSON都改成Flat的了:

{
  "name" : "John Appleseed",
  "id" : 7,
  "gift" : "Teddy Bear"
}

Employee數(shù)據(jù)結(jié)構(gòu)不一樣,客戶端gift是一個Toy對象而非字符串,編譯器無法自動生成,只能自己寫decode(init)和encode方法了:

struct Toy: Codable {
  var name: String
}

struct Employee: Encodable {
  var name: String
  var id: Int
  var favoriteToy: Toy

  enum CodingKeys: CodingKey {
    case name, id, gift
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self) // 聲明一個container,可以像Dictionory用指定鍵存儲內(nèi)容

    try container.encode(name, forKey: .name)
    try container.encode(id, forKey: .id)
    try container.encode(favoriteToy.name, forKey: .gift)  // 關(guān)鍵:指定key為gift去encode favoriteToy的內(nèi)容
  }
}

// 寫在extension里是為了保持Swift struct的member-wise initializer, 如果在struct的主定義里寫了init方法就會導(dǎo)致失效
extension Employee: Decodable {
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    name = try container.decode(String.self, forKey: .name)
    id = try container.decode(Int.self, forKey: .id)

    let gift = try container.decode(String.self, forKey: .gift)
    favoriteToy = Toy(name: gift)
  }
}

let toy = Toy(name: "Teddy Bear")
let employee = Employee(name: "John Appleseed", id: 7, favoriteToy: toy)

let encoder = JSONEncoder()
let decoder = JSONDecoder()

let data = try encoder.encode(employee)
let string = String(data: data, encoding: .utf8)!
let sameEmployee = try decoder.decode(Employee.self, from: data)
Deep JSON

假如API把JSON的數(shù)據(jù)結(jié)構(gòu)又改成了這樣:

{
  "name" : "John Appleseed",
  "id" : 7,
  "gift" : {
    "toy" : {
      "name" : "Teddy Bear"
    }
  }
}

又和Employee結(jié)構(gòu)不一樣,還是得像flat json的情況一樣,重寫encode和decode(init)方法,變化點(diǎn):嵌套層加一個Keys枚舉;嵌套層通過nestedContainer裝入外層

struct Employee: Encodable {
  var name: String
  var id: Int
  var favoriteToy: Toy

  enum CodingKeys: CodingKey {
    case name, id, gift
  }

  enum GiftKeys: CodingKey {
    case toy
  }

  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)
    // 在外層container基礎(chǔ)上聲明嵌套container
    var giftContainer = container.nestedContainer(keyedBy: GiftKeys.self, forKey: .gift)
    try giftContainer.encode(favoriteToy, forKey: .toy)
  }
}

extension Employee: Decodable {
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    name = try container.decode(String.self, forKey: .name)
    id = try container.decode(Int.self, forKey: .id)
    // 從外層container中獲取嵌套container
    let giftContainer = try container.nestedContainer(keyedBy: GiftKeys.self, forKey: .gift)
    favoriteToy = try giftContainer.decode(Toy.self, forKey: .toy)
  }
}
Date

JSONEncoderJSONDecoder可以定制dateStrategy,系統(tǒng)提供了幾種策略如secondsSince1970``millisecondsSince1970,默認(rèn)使用deferredToDate。但日期的表示往往視實(shí)際項(xiàng)目而定,這時可以通過extension拓展DateFormatter

extension DateFormatter {
  static let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "dd-MM-yyyy"
    return formatter
  }()
}
// ......
encoder.dateEncodingStrategy = .formatted(.dateFormatter)
decoder.dateDecodingStrategy = .formatted(.dateFormatter)

.formatted策略只是按String的方式處理Date,DateEncodingStrategy的策略實(shí)際上有很多種,以應(yīng)付各種格式的日期

/// The strategy to use for encoding `Date` values.
    public enum DateEncodingStrategy {

        /// Defer to `Date` for choosing an encoding. This is the default strategy.
        case deferredToDate

        /// Encode the `Date` as a UNIX timestamp (as a JSON number).
        case secondsSince1970

        /// Encode the `Date` as UNIX millisecond timestamp (as a JSON number).
        case millisecondsSince1970

        /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
        @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
        case iso8601

        /// Encode the `Date` as a string formatted by the given formatter.
        case formatted(DateFormatter)

        /// Encode the `Date` as a custom value encoded by the given closure.
        ///
        /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
        case custom((Date, Encoder) throws -> Void)
    }

對于客戶端來說,尤其是Decode,通常應(yīng)該覆蓋可能的各種格式,譬如:

decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
  let decoder = JSONDecoder()
  decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
      let container = try decoder.singleValueContainer()
      let dateStr = try container.decode(String.self)
      // possible date strings: "2016-05-01",  "2016-07-04T17:37:21.119229Z", "2018-05-20T15:00:00Z"
      let len = dateStr.count
      var date: Date? = nil
      if len == 10 {
          date = dateNoTimeFormatter.date(from: dateStr)
      } else if len == 20 {
          date = isoDateFormatter.date(from: dateStr)
      } else {
          date = self.serverFullDateFormatter.date(from: dateStr)
      }
      guard let date_ = date else {
          throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateStr)")
      }
      print("DATE DECODER \(dateStr) to \(date_)")
      return date_
  })
  return decoder
})
Subclasses

如果JSON數(shù)據(jù)結(jié)構(gòu)是這樣的:

{
  "toy" : {
    "name" : "Teddy Bear"
  },
  "employee" : {
    "name" : "John Appleseed",
    "id" : 7
  },
  "birthday" : 580794178.33482599
}

如果把employee字段包含的內(nèi)容看做一個基礎(chǔ)父類BasicEmployee,加入的toy和birthday其實(shí)是對BasicEmployee的拓展,當(dāng)子類想利用父類已有的encode 和 decode方法:

class GiftEmployee: BasicEmployee {
  var birthday: Date
  var toy: Toy

  enum CodingKeys: CodingKey {
    case employee, birthday, toy
  }
  
  init(name: String, id: Int, birthday: Date, toy: Toy) {
    self.birthday = birthday
    self.toy = toy
    super.init(name: name, id: id)
  }

  required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    birthday = try container.decode(Date.self, forKey: .birthday)
    toy = try container.decode(Toy.self, forKey: .toy)

    // 生成父類的decoder用于傳給父類的init方法
    let baseDecoder = try container.superDecoder(forKey: .employee)
    try super.init(from: baseDecoder)
  }

  override func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(birthday, forKey: .birthday)
    try container.encode(toy, forKey: .toy)

    // 生成父類的encoder用于傳給父類的encode方法
    let baseEncoder = container.superEncoder(forKey: .employee)
    try super.encode(to: baseEncoder)
  }
}

Handling Arrays With Mixed Types

如果JSON數(shù)據(jù)結(jié)構(gòu)是這樣的:

[
  {
    "name" : "John Appleseed",
    "id" : 7
  },
  {
    "id" : 7,
    "name" : "John Appleseed",
    "birthday" : 580797832.94787002,
    "toy" : {
      "name" : "Teddy Bear"
    }
  }
]

是一個混合了不同類型的數(shù)組,解決方案:

struct Toy: Codable {
  var name: String
}
let toy = Toy(name: "Teddy Bear")
let encoder = JSONEncoder()
let decoder = JSONDecoder()

enum AnyEmployee: Encodable {
  case defaultEmployee(String, Int)
  case customEmployee(String, Int, Date, Toy)
  case noEmployee

  enum CodingKeys: CodingKey {
    case name, id, birthday, toy
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)

    switch self {
    case .defaultEmployee(let name, let id):
      try container.encode(name, forKey: .name)
      try container.encode(id, forKey: .id)
    case .customEmployee(let name, let id, let birthday, let toy):
      try container.encode(name, forKey: .name)
      try container.encode(id, forKey: .id)
      try container.encode(birthday, forKey: .birthday)
      try container.encode(toy, forKey: .toy)
    default:
      let context = EncodingError.Context(codingPath: encoder.codingPath,
                                          debugDescription: "Invalid employee!")
      throw EncodingError.invalidValue(self, context)
    }
  }
}

extension AnyEmployee: Decodable {
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let containerKeys = Set(container.allKeys)
    let defaultKeys = Set<CodingKeys>([.name, .id])
    let customKeys = Set<CodingKeys>([.name, .id, .birthday, .toy])

    switch containerKeys {
    case defaultKeys:
      let name = try container.decode(String.self, forKey: .name)
      let id = try container.decode(Int.self, forKey: .id)
      self = .defaultEmployee(name, id)
    case customKeys:
      let name = try container.decode(String.self, forKey: .name)
      let id = try container.decode(Int.self, forKey: .id)
      let birthday = try container.decode(Date.self, forKey: .birthday)
      let toy = try container.decode(Toy.self, forKey: .toy)
      self = .customEmployee(name, id, birthday, toy)
    default:
      self = .noEmployee
    }
  }
}

let employees = [AnyEmployee.defaultEmployee("John Appleseed", 7),
                 AnyEmployee.customEmployee("John Appleseed", 7, Date(), toy)]
let employeesData = try encoder.encode(employees)
let employeesString = String(data: employeesData, encoding: .utf8)!
let sameEmployees = try decoder.decode([AnyEmployee].self, from: employeesData)

Array

如果JSON直接下發(fā)了一個沒有key的數(shù)組,如:

[
  "teddy bear",
  "TEDDY BEAR",
  "Teddy Bear"
]

就需要用到unkeyedContainer

struct Toy: Codable {
  var name: String
}

let toy = Toy(name: "Teddy Bear")

let encoder = JSONEncoder()
let decoder = JSONDecoder()

struct Label: Encodable {
  var toy: Toy

  func encode(to encoder: Encoder) throws {
    var container = encoder.unkeyedContainer()
    try container.encode(toy.name.lowercased())
    try container.encode(toy.name.uppercased())
    try container.encode(toy.name)
  }
}

extension Label: Decodable {
  init(from decoder: Decoder) throws {
    var container = try decoder.unkeyedContainer()
    var name = ""
    while !container.isAtEnd {
      name = try container.decode(String.self)
    }
    toy = Toy(name: name)
  }
}

let label = Label(toy: toy)
let labelData = try encoder.encode(label)
let labelString = String(data: labelData, encoding: .utf8)!
let sameLabel = try decoder.decode(Label.self, from: labelData)

Array Within Objects

假如數(shù)據(jù)結(jié)構(gòu)是這樣:

{
  "name" : "Teddy Bear",
  "label" : [
    "teddy bear",
    "TEDDY BEAR",
    "Teddy Bear"
  ]
}

需要使用nestedUnkeyedContainer


let encoder = JSONEncoder()
let decoder = JSONDecoder()

struct Toy: Encodable {
  var name: String
  var label: String
  
  enum CodingKeys: CodingKey {
    case name, label
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(name, forKey: .name)
    var labelContainer = container.nestedUnkeyedContainer(forKey: .label)
    try labelContainer.encode(name.lowercased())
    try labelContainer.encode(name.uppercased())
    try labelContainer.encode(name)
  }
}

extension Toy: Decodable {
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    name = try container.decode(String.self, forKey: .name)
    var labelContainer = try container.nestedUnkeyedContainer(forKey: .label)
    var labelName = ""
    while !labelContainer.isAtEnd {
      labelName = try labelContainer.decode(String.self)
    }
    label = labelName
  }
}

let toy = Toy(name: "Teddy Bear", label: "Teddy Bear")
let data = try encoder.encode(toy)
let string = String(data: data, encoding: .utf8)!
let sameToy = try decoder.decode(Toy.self, from: data)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容