Encoding and Decoding in Swift笔记

2019-12-30  本文已影响0人  ameerkat

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

基础语法

Swift将 encoding和 decoding能力内置在了系统库(Codable协议)中,不需要像OC一样编写从JSON String 到 对象的decoding方法(及其逆方法),我们所需要做的只有:

struct Toy: Codable {
  var name: String
}
struct Employee: Codable {
  var name: String
  var id: Int
  var favoriteToy: Toy
}
let encoder = JSONEncoder()
let decoder = JSONDecoder()

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协议的能力

嵌套编码

一段用utf8编码过得JSON下发到客户端,实际上就相当于上述代码中的string,格式化处理后相当于:

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

对于favoriteToy字段下嵌套的可以对应到Toy的JSON结构,由于Toy类型也遵守了Codable协议,也会被正确转为Toy对象

Snake Case 和 Camel Case之间的转换

可以帮助解决,客户端常用Camel Case 但 API可能使用Snake Case的问题:

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

猜测内部会对key先做一层转换再去encode/decode

自定义JSON Key

可能出现客户端和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枚举的变量才会被解析/编码,所以即使不需要自定义,需要的变量名仍要写进去

处理与客户端不同的JSON数据结构

Flat JSON

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

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

Employee数据结构不一样,客户端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用指定键存储内容

    try container.encode(name, forKey: .name)
    try container.encode(id, forKey: .id)
    try container.encode(favoriteToy.name, forKey: .gift)  // 关键:指定key为gift去encode favoriteToy的内容
  }
}

// 写在extension里是为了保持Swift struct的member-wise initializer, 如果在struct的主定义里写了init方法就会导致失效
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的数据结构又改成了这样:

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

又和Employee结构不一样,还是得像flat json的情况一样,重写encode和decode(init)方法,变化点:嵌套层加一个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基础上声明嵌套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,系统提供了几种策略如secondsSince1970``millisecondsSince1970,默认使用deferredToDate。但日期的表示往往视实际项目而定,这时可以通过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的策略实际上有很多种,以应付各种格式的日期

/// 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,通常应该覆盖可能的各种格式,譬如:

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数据结构是这样的:

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

如果把employee字段包含的内容看做一个基础父类BasicEmployee,加入的toy和birthday其实是对BasicEmployee的拓展,当子类想利用父类已有的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数据结构是这样的:

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

是一个混合了不同类型的数组,解决方案:

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直接下发了一个没有key的数组,如:

[
  "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

假如数据结构是这样:

{
  "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)
上一篇下一篇

猜你喜欢

热点阅读