Swift 4的Codable
几乎可以肯定的是,在应用程序开发过程中的某个时刻,您需要(或者您将很快:])序列化一些对象或值,并解析对模型的JSON响应。 如果我是对的,那么这可能值得您花时间。
在Swift中,我们曾经使用NSCoding
协议来序列化对象。 为了符合NSCoding
我们常常最终使类从NSObject
继承,然后经过一会儿cha和samba以及异常长的时间之后,我们最终设法实现了预期的init(coder:)
和encode(with:)
方法。 但是structs
正确吗? 好吧,大多数人在将第三方框架引入其项目时找到了解决方案。 希望有关序列化和映射的斗争已经结束。
现在怎么办?
Swift 4引入了一个称为Codable
的协议,该协议由其他两个协议组成: Encodable
和Decodable
。 Codable
允许我们几乎无需费力地对classes
, structs
和enums
进行序列化和反序列化。 包括String
, Int
, Array
, Dictionary
在内的大多数类型都符合此协议。 因此,当我们创建一个包含已经符合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
有两个很棒的用途:
- 制图
- 档案与非档案
制图
假设您以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
类型,而不是从服务呼叫中接收到的latitude
和longitude
值。 或者,我希望以各种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
将能够使用默认实现来处理它。
档案与非档案
NSKeyedArchiver
和NSKeyedUnarchiver
还支持Codable
。 这是我们如何存档值:
NSKeyedArchiver.archiveRootObject(person,toFile:“文件”)
除了符合NSCoding
类之外,现在所有符合Codable
类型都可以由NSKeyedArchiver
处理。
取消存档,您可以在这里进行:
守卫让数据= NSKeyedUnarchiver.unarchiveObject(withFile:“ file”)为? 其他数据{返回}
如果选择使用JSONEncoder
编码值然后将其存档,则可以在取消存档后使用JSONDecoder
对其进行解码。
此功能使我们能够持久存储我们的Codable
类型。
最后说明
使用Codable
,您可能会遇到各种情况,例如作为响应作为根级数组接收到时将Codable
类型初始化为数组,或者如何执行转换(感谢Evan Roth和GökselKöksal指出了注释中的内容) 。 如果您遇到此类问题或有任何改进的想法,请立即评论以下内容:]