美文网首页
Encoding and Decoding in Swift笔记

Encoding and Decoding in Swift笔记

作者: ameerkat | 来源:发表于2019-12-30 18:03 被阅读0次

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

    基础语法

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

    • 遵守Codable协议
    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()
    
    
    • 执行编码和解码
    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)
    

    相关文章

      网友评论

          本文标题:Encoding and Decoding in Swift笔记

          本文链接:https://www.haomeiwen.com/subject/kzimoctx.html