Swift 4 Decodable: Beyond The Basics 📦

Swift 4可解码:超越基础

One of the features that I was looking forward to this year WWDC was Codable, which is just a type alias of the Encodableand Decodable protocols.

  • 我期待今年WWDC的一个功能是Codable,它只是Encodable和Decodable协议的类型别名。

I’ve just spent a whole week shifting my Swift projects from using custom JSON parsers to Decodable(while removing a lot of code! 🎉), this post showcases what I’ve learned along the way.

  • 我花了整整一周时间将我的Swift项目从使用自定义JSON解析器转移到Decodable(同时删除大量代码!🎉),这篇文章展示了我在学习过程中学到的东西。
image.png

新加坡吉宝集装箱码头

Basics 基本

If you haven’t seen it already, I suggest you to watch the related WWDC session(the Codable part starts past 23 minutes).

  • 如果您还没有看过,我建议您观看相关的WWDC会话(Codable部分从23分钟开始)。

In short: you can now convert a set of data from a JSON Object or Property List to an equivalent StructorClass, basically without writing a single line of code.

  • 简而言之:您现在可以将一组数据从JSON对象或属性列表转换为等效的StructorClass,基本上无需编写任何代码。

Here’s an example (it’s a Swift Playground!):

  • 这是一个例子(它是一个Swift游乐场!):
let json = """
{
 "fullName": "Federico Zanetello",
 "id": 123456,
 "twitter": "http://twitter.com/zntfdr"
}
""".data(using: .utf8)! // our data in native (JSON) format

import Foundation

struct Swifter: Decodable {
    let fullName: String
    let id: Int
    let twitter: URL
}

let myStruct = try JSONDecoder().decode(Swifter.self, from: json)
print(myStruct)

如图所示:


image.png

What the Compiler Does Without Us Noticing

If we look at the Decodable documentation, we see that the protocol requires the implementation of a init(from: Decoder) method.

  • 如果我们查看Decodable文档,我们会看到协议要求实现init(from:Decoder)方法。

We didn’t implemented it in the Playground: why did it work?

  • 我们没有在Playground中实现它:为什么它有效?

It’s for the same reason why we don’t have to implement a Swifter initializer, but we can still go ahead and initialize our struct: the Swift compiler provides one for us! 🙌

  • 这也是为什么我们不必实现Swifter初始化器的原因,但我们仍然可以继续初始化我们的结构:Swift编译器为我们提供了一个!🙌
image.png

Conforming to Decodable

All of the above is great and just works™ as long as all we need to parse is a subsets of primitives (strings, numbers, bools, etc) or other structures that conform to the Decodable protocol.

  • 只要我们需要解析的是原语(例如:字符串,数字,布尔等)或符合可解码协议的其他结构的子集,上述所有内容都很棒并且正常工作。

But what about parsing more “complex structures”?
Well, in this case we have to do some work.

  • 但是解析更多“复杂结构”呢?
    那么,在这种情况下,我们必须做一些工作。

Implementing init(from: Decoder)

⚠️ This part might be a bit trickier to understand: everything will be clear with the examples below!

  • 这一部分可能有点难以理解:下面的示例将解释清楚一切!

Before diving into our own implementation of this initializer, let’s take a look at the main players:

  • 在深入了解我们自己的初始化程序实现之前,让我们来看看主要的工具类

1、The Decoder

As the name implies, the Decoder transforms something into something else: in our case this means moving from a native format (e.g. JSON) into an in-memory representation.

  • 顾名思义,解码器将某些内容转换为其他内容:在我们的示例中,这意味着从原生格式(例如JSON)转换为内存中表示。

We will focus on two of the Decoder’s functions:

  • 我们将重点介绍Decoder的两个功能:
  1. container<Key>(keyedBy: Key.Type)
  2. singleValueContainer()

In both cases, the Decoder returns a (Data) Container.

  • 在这两种情况下,解码器都返回一个(数据)容器。

Now you know what’s up with all these container pictures! 😜
现在你知道所有这些容器图片的内容了!😜

With the first function, the Decoder returns a keyed container, KeyedDecodingContainer: to reach the actual data, we must first tell the container which keys to look for (more on this later!).

  • 使用第一个函数,Decoder返回一个键控容器KeyedDecodingContainer:为了获得实际数据,我们必须首先告诉容器要查找哪些键(稍后会详细介绍!)。

The second function tells the decoder that there’s no key: the returned container, SingleValueDecodingContainer, is actually the data that we want!

  • 第二个函数告诉解码器没有密钥:返回的容器SingleValueDecodingContainer实际上是我们想要的数据!

2、The Containers

Thanks to our Decoder we’ve moved from a raw native format to a structure that we can play with (our containers). Time to extract our data! Let’s take a look at the two containers that we’ve just discovered:

  • 感谢我们的解码器,我们已经从原始原生格式转变为我们可以使用的结构(我们的容器)。 是时候提取我们的数据! 让我们来看看我们刚发现的两个容器:
2.1、KeyedDecodingContainer

In this case we know that our container is keyed, you can think of this container as a dictionary [Key: Any].

  • 在这种情况下,我们知道我们的容器是键控的,您可以将此容器视为字典[Key:Any]。

Different keys can hold different types of data: which is why the container offers several decode(Type:forKey:) methods.

  • 不同的键可以持有不同类型的数据:这就是容器提供多种decode(Type:forKey :)方法的原因。

This method is where the magic happens: by calling it, the container returns us our data’s value of the given type for the given key (examples below!).

  • 这种方法神奇的地方是:通过调用它,容器返回给定键的给定类型的数据值(下面的示例!)。

Most importantly, the container offers the generic method
decode<T>(T.Type, forKey: K) throws -> T where T: Decodable which means that any type, as long as it conforms to Decodable, can be used with this function! 🎉🎉

  • 最重要的是,容器提供了泛型方法
    decode <T>(T.Type,forKey:K)throws - > T where T:Decodable表示任何类型,只要它符合Decodable,就可以与此函数一起使用!🎉🎉
2.2、SingleValueDecodingContainer

Everything works as above, just without any keys.

  • 一切都如上所述,只不过不使用任何键。
image.png

Implementing our init(from: Decoder)

We’ve seen all the players that will help us go from data stored in our disk to data that we can use in our App: let’s put them all together!

  • 我们已经看到所有能够帮助我们将存储在磁盘中的数据转到我们可以在我们的应用程序中使用的数据的工具类:让我们把它们放在一起!

Take the playground at the start of the article for example: instead of letting the compiler doing it for us, let’s implement our own init(from: Decoder).

  • 以文章开头的游乐场为例:不要让编译器为我们做,而是让我们实现自己的 init(from: Decoder)。

Step 1: Choosing The Right Decoder 选择正确的解码器

The example’s data is a JSON object, therefore we will use the Swift Library’sJSONDecoder.

  • 示例的数据是JSON对象,因此我们将使用Swift Library的JSONDecoder。
let decoder = JSONDecoder()

⚠️ JSON and P-list encoders and decoders are embedded in the Swift Library: you can write your own coders to support different formats!

  • ⚠️JSON和P-list编码器和解码器嵌入在Swift库中:您可以编写自己的编码器来支持不同的格式!

Step 2: Determining The Right Container 确定合适的容器

In our case the data is keyed:

  • 在我们的例子中,数据是键控的:
{
 "fullName": "Federico Zanetello",
 "id": 123456,
 "twitter": "http://twitter.com/zntfdr"
}

To reach "Federico Zanetello" we must ask for the value of key "fullName", to reach 123456 we must ask for the valued of index "id", etc.

  • 要获得“Federico Zanetello”,我们通过键“fullName”来获得这个值,要获得123456,我们通过键“id”来获得这个值。

Therefore, we must use a KeyedDecodingContainer Container (by calling the Decoder’s method container<Key>(keyedBy: Key.Type)).

  • 因此,我们必须使用KeyedDecodingContainer容器(通过调用Decoder的方法container <Key>(keyedBy:Key.Type))。

But before doing so, as requested by the method, we must declare our keys: Key is actually a protocol and the easiest way to implement it is by declaring our keys as an enum of type String:

  • 但在这样做之前,按照方法的要求,我们必须声明我们的键:Key实际上是一个协议,实现它的最简单方法是将我们的键声明为String类型的枚举:
enum MyStructKeys: String, CodingKey {
  case fullName = "fullName"
  case id = "id"
  case twitter = "twitter"
}

Note: you don’t have to write = “…” in each case: but for clarity’s sake I’ve chosen to write it.

  • 注意:在每种情况下你都不必写=“...”:但为了清楚起见,我选择写它。

Now that we have our keys set up, we can go on and create our container:

  • 现在我们已经设置了键,我们可以继续创建我们的容器:
let container = try decoder.container(keyedBy: MyStructKeys.self)

Step 3: Extracting Our Data

Finally, we must convert the container’s data into something that we can use in our app:

  • 最后,我们必须将容器的数据转换为我们可以在应用程序中使用的内容:
let fullName: String = try container.decode(String.self, forKey: .fullName)
let id: Int = try container.decode(Int.self, forKey: .id)
let twitter: URL = try container.decode(URL.self, forKey: .twitter)

Step 4: Initializing our Struct/Class

We can use the default Swifter initializer:

  • 我们可以使用默认的Swifter初始化器:
let myStruct = Swifter(fullName: fullName, id: id, twitter: twitter)

Voila! We’ve just implemented Decodable all by ourselves! 👏🏻👏🏻
Here’s the final playground:

  • 瞧! 我们刚刚自己实现了Decodable!👏🏻👏🏻
    这是最后的游乐场:
import Foundation

struct Swifter {
    let fullName: String
    let id: Int
    let twitter: URL
    
    init(fullName: String, id: Int, twitter: URL) { // default struct initializer
        self.fullName = fullName
        self.id = id
        self.twitter = twitter
    }
}

extension Swifter: Decodable {
    enum MyStructKeys: String, CodingKey { // declaring our keys
        case fullName = "fullName"
        case id = "id"
        case twitter = "twitter"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: MyStructKeys.self) // defining our (keyed) container
    // 第一种写法
        let fullName: String = try container.decode(String.self, forKey: .fullName) // extracting the data
        let id: Int = try container.decode(Int.self, forKey: .id) // extracting the data
        let twitter: URL = try container.decode(URL.self, forKey: .twitter) // extracting the data
        self.init(fullName: fullName, id: id, twitter: twitter) // initializing our struct

    // 第二种写法
        fullName = try container.decode(String.self, forKey: .fullName)
        id = try container.decode(Int.self, forKey: .id)
        twitter = try container.decode(URL.self, forKey: .twitter)
    }
}
let json = """
{
 "fullName": "Federico Zanetello",
 "id": 123456,
 "twitter": "http://twitter.com/zntfdr"
}
""".data(using: .utf8)! // our native (JSON) data
let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // decoding our data
print(myStruct) // decoded!

image.png

Going further (More Playgrounds!)

Now that our Swifter struct conforms to Decodable, any other struct/class/etc that contains such data can automatically decode Swifter for free. For example:

  • 既然我们的Swifter结构符合Decodable,那么包含这些数据的任何其他struct / class / etc都可以自动解码Swifter。 例如:

Arrays

import Foundation

struct Swifter: Decodable {
    let fullName: String
    let id: Int
    let twitter: URL
}
let json = """
[{
 "fullName": "Federico Zanetello",
 "id": 123456,
 "twitter": "http://twitter.com/zntfdr"
},{
 "fullName": "Federico Zanetello",
 "id": 123456,
 "twitter": "http://twitter.com/zntfdr"
},{
 "fullName": "Federico Zanetello",
 "id": 123456,
 "twitter": "http://twitter.com/zntfdr"
}]
""".data(using: .utf8)! // our data in native format
let myStructArray = try JSONDecoder().decode([Swifter].self, from: json)

myStructArray.forEach { print($0) } // decoded!!!!!

Dictionaries

import Foundation

struct Swifter: Codable {
    let fullName: String
    let id: Int
    let twitter: URL
}
let json = """
{
  "one": {
    "fullName": "Federico Zanetello",
    "id": 123456,
    "twitter": "http://twitter.com/zntfdr"
  },
  "two": {
    "fullName": "Federico Zanetello",
    "id": 123456,
    "twitter": "http://twitter.com/zntfdr"
  },
  "three": {
    "fullName": "Federico Zanetello",
    "id": 123456,
    "twitter": "http://twitter.com/zntfdr"
  }
}
""".data(using: .utf8)! // our data in native format
let myStructDictionary = try JSONDecoder().decode([String: Swifter].self, from: json)

myStructDictionary.forEach { print("\($0.key): \($0.value)") } // decoded!!!!!

Enums

import Foundation

struct Swifter: Decodable {
  let fullName: String
  let id: Int
  let twitter: URL
}

enum SwifterOrBool {
  case swifter(Swifter)
  case bool(Bool)
}

extension SwifterOrBool: Decodable {
  enum CodingKeys: String, CodingKey {
    case swifter, bool
  }
  
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    if let swifter = try container.decodeIfPresent(Swifter.self, forKey: .swifter) {
      self = .swifter(swifter)
    } else {
      self = .bool(try container.decode(Bool.self, forKey: .bool))
    }
  }
}
let json = """
[{
"swifter": {
   "fullName": "Federico Zanetello",
   "id": 123456,
   "twitter": "http://twitter.com/zntfdr"
  }
},
{ "bool": true },
{ "bool": false },
{
"swifter": {
   "fullName": "Federico Zanetello",
   "id": 123456,
   "twitter": "http://twitter.com/zntfdr"
  }
}]
""".data(using: .utf8)! // our native (JSON) data
let myEnumArray = try JSONDecoder().decode([SwifterOrBool].self, from: json) // decoding our data
  
myEnumArray.forEach { print($0) } // decoded!

More Complex Structs

import Foundation

struct Swifter: Decodable {
    let fullName: String
    let id: Int
    let twitter: URL
}

struct MoreComplexStruct: Decodable {
    let swifter: Swifter
    let lovesSwift: Bool
}
let json = """
{
    "swifter": {
        "fullName": "Federico Zanetello",
        "id": 123456,
        "twitter": "http://twitter.com/zntfdr"
    },
    "lovesSwift": true
}
""".data(using: .utf8)! // our data in native format
let myMoreComplexStruct = try JSONDecoder().decode(MoreComplexStruct.self, from: json)

print(myMoreComplexStruct.swifter) // decoded!!!!!
image.png

Before Departing

In all probability, during your first Decodable implementations, something will go wrong: maybe it’s a key mismatch, maybe it’s a type mismatch, etc.

  • 很可能,在你的第一个Decodable实现期间,会出现问题:也许这是一个关键的不匹配,也许是类型不匹配等。

To detect all of these errors early, I suggest you to use Swift Playgrounds with error handling as much as possible:

  • 为了尽早发现所有这些错误,我建议您尽可能使用Swift Playgrounds进行错误处理:
do {
  let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // do your decoding here
} catch {
  print(error) // any decoding error will be printed here!
}

Conclusion:

  • 1、从上面的不同版本的json数据我们得出一个结论,那就是不管数据多么简单,用一个字典将数据包裹,也不过数据有多么复杂,也是用字典嵌套在里面,如果是数组里面的元素是数组元素,那么每次取出一个字典,然后通过key,获取value 值
  • 2、如果我们实现了Decodeable里面的初始化方法,那么编译器便不会帮我们创建默认进行实线,调用Decodeable里面的初始化方法,然后初始化store property,最后返回新创建的结构体对象
image.png
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容