Swift 4的Codable

几乎可以肯定的是,在应用程序开发过程中的某个时刻,您需要(或者您将很快:])序列化一些对象或值,并解析对模型的JSON响应。 如果我是对的,那么这可能值得您花时间。

在Swift中,我们曾经使用NSCoding协议来序列化对象。 为了符合NSCoding我们常常最终使类从NSObject继承,然后经过一会儿cha和samba以及异常长的时间之后,我们最终设法实现了预期的init(coder:)encode(with:)方法。 但是structs正确吗? 好吧,大多数人在将第三方框架引入其项目时找到了解决方案。 希望有关序列化和映射的斗争已经结束。

现在怎么办?

Swift 4引入了一个称为Codable的协议,该协议由其他两个协议组成: EncodableDecodableCodable允许我们几乎无需费力地对classesstructsenums进行序列化和反序列化。 包括StringIntArrayDictionary在内的大多数类型都符合此协议。 因此,当我们创建一个包含已经符合Codable属性的新类型时,我们无需再编写一行代码。 如下所示, Person类型符合Codable

  struct Person:可编码{
   命名:字符串
   年龄:整数
   让朋友们:[人]?
 } 

Person 序列 Person JSON:

  let person = Person(姓名:“ Brad”,年龄:53,朋友:无) 
 //对不起,Bradlet编码器= JSONEncoder()
让jsonData =试试encoder.encode(person)String(data:jsonData,编码:.utf8)!  // {“ name”:“ Brad”,“ age”:53} 

Voilà,人员价值就这样转换为数据。

现在让我们反序列化

 让解码器= JSONDecoder()让解码=尝试解码器.decode(Person.self,来自:jsonData)
 type(of:已解码)// Person 

对我有什么用?

我发现Codable有两个很棒的用途:

  1. 制图
  2. 档案与非档案

制图

假设您以JSON字符串的形式接收到响应(如果它是编码数据,则更好)。 使用该JSON字符串初始化我们的类型会很好,对吗? 让我们写下来:

 协议可映射:可解码{
   初始化?(jsonString:String)
 }扩展名可映射{
   初始化?(jsonString:String){
      警卫让数据= jsonString.data(使用:.utf8),否则{ 
         返回零 
       }
      自我=尝试!  JSONDecoder()。decode(Self.self,来自:数据)        
       //为了简单起见,我使用了强制展开。
       //最好使用do-catch处理异常。
    }
 } 

再次由于功能强大的协议扩展,我们无需对类型进行任何进一步的处理(在Swift 4中也引入了下面带有三引号的多行字符串)。 而且我们不符合包含Encodable因为我们只想从JSON映射到Mappable类型。

 让jsonString =“”“
 {
 \“名称\”:\“布拉德\”,
 “年龄”:53
 }
 “” 

Person一点重构:

  struct Person:可映射{
   命名:字符串
   年龄:整数
   让朋友们:[人]?
 }让newPerson = Person(jsonString:jsonString)
 type(of:newPerson)//人员? 

需要为Mappable初始化程序添加可选性,以避免由于响应中缺少字段而可能导致的失败。 并且当我们将属性Codable可选(在这种情况下为friends )时,如果可选字段未包含在数据中,则Codable仍将起作用。

如果为此使用了诸如ObjectMapper类的第三方框架,这真是个好消息,那么您就不再需要。 它不仅已经提供了相同的映射功能,而且还使您避免了使用下标键来查找值。 它从属性名称推断键。 Swift 4还提供了CodingKey协议,使您可以CodingKey命名属性:

  struct Person:可映射{
   让别名:字符串
   年龄:整数
   让同志们:[人]?
   
   私有枚举CodingKeys:字符串,CodingKey {
      案例别名=“名称”
      案件年龄
      案例同志=“朋友”
    }
 } 

因为可以,我将name更改为alias ,将friends更改为comrades

转变

除了使用CodingKey修改响应参数的命名CodingKey ,我们还可以根据响应进行结构更改。 一种情况是使用您自己的Coordinate类型,而不是从服务呼叫中接收到的latitudelongitude值。 或者,我希望以各种Date类型的格式存储日期字符串。 为此,我们将需要自己实现Decodable协议的init(from:)方法。

让我们重新实现Person类型:

  struct Person:可映射{
  生:日期
  让位置:坐标
  让名字:字符串? 枚举CodingKeys:字符串,CodingKey {
    案例出生=“出生日期” 
     //响应将包含lat和long,但 
     //我们将存储为坐标
    拉特  
    案子长
    案例名称
   } init(来自解码器:Decoder)抛出{
    让值=尝试解码器。容器(keyedBy:CodingKeys.self)让出生字符串=尝试值。解码(字符串.self,forKey :.出生)
    让formatter = DateFormatter()
     formatter.dateFormat =“ MM-dd-yyyy”
    出生= formatter.date(来自:birthString)!  //解析经纬度以初始化坐标
    让lat =试试values.decode(Float.self,forKey:.lat)
    让长=尝试values.decode(Float.self,forKey:.long)
    位置=坐标(纬度:经度,长度:长)名称=尝试值.decodeIfPresent(String.self,forKey:.name)
   }
 } 

以及用法:

 让jsonString =“”“
 {
 \“出生日期\”:\“ 1982年11月21日\”,
 \“ lat \”:29.321,
 \“长\”:36.119
 }
 “”
 let person = Person(jsonString:jsonString)
 person!.position.lat // 29.321
 type(of:person!.birth)// Date.Type
人!。名称//无 

我们使用了CodingKeys枚举来解析响应中的CodingKeys ,并使用DateFormatter解析出Date属性而不是String来解析出生属性(稍后我将再次讨论)。 注意name是nil,因为jsonString不包含name键和值。 对于我们的可选类型,我们必须使用decodeIfPresent方法而不是decode方法。 不利的一面是,如果我们只对一个具有十个属性的类型的一个属性执行转换,则由于无法再使用默认扩展名,我们也将不得不以通常的方式实现所有其他属性的赋值。

转换Date的更好方法是更新我们的Mappable扩展,如下所示:

  ...
让jsonDecoder = JSONDecoder()
让formatter = DateFormatter()
 formatter.dateFormat =“ MM-dd-yyyy”
 jsonDecoder.dateDecodingStrategy = .formatted(formatter)self =试试!  jsonDecoder.decode(Self.self,来自:数据)
 ... 

这样,我们告诉JSONDecoder如何将String解析为Date ,而不是自己从String创建Date类型。 所以这:

 让birthString =尝试values.decode(String.self,forKey:.birth)
让formatter = DateFormatter()
 formatter.dateFormat =“ MM-dd-yyyy”
出生= formatter.date(来自:birthString)! 

现在每个Date分配仅一行:

 出生=尝试values.decode(Date.self,forKey:.birth) 

而且,如果我们仅需要进行有关Date类型的转换,则不再需要实现init(from decoder: Decoder)因为JSONDecoder将能够使用默认实现来处理它。

档案与非档案

NSKeyedArchiverNSKeyedUnarchiver还支持Codable 。 这是我们如何存档值:

  NSKeyedArchiver.archiveRootObject(person,toFile:“文件”) 

除了符合NSCoding类之外,现在所有符合Codable类型都可以由NSKeyedArchiver处理。

取消存档,您可以在这里进行:

 守卫让数据= NSKeyedUnarchiver.unarchiveObject(withFile:“ file”)为? 其他数据{返回} 

如果选择使用JSONEncoder编码值然后将其存档,则可以在取消存档后使用JSONDecoder对其进行解码。

此功能使我们能够持久存储我们的Codable类型。

最后说明

使用Codable ,您可能会遇到各种情况,例如作为响应作为根级数组接收到时将Codable类型初始化为数组,或者如何执行转换(感谢Evan Roth和GökselKöksal指出了注释中的内容) 。 如果您遇到此类问题或有任何改进的想法,请立即评论以下内容:]