- Google question: what is keyed decoding container
- 翻译自: https://medium.com/swiftly-swift/swift-4-decodable-beyond-the-basics-990cc48b7375
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(同时删除大量代码!🎉),这篇文章展示了我在学习过程中学到的东西。
新加坡吉宝集装箱码头
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)
如图所示:
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编译器为我们提供了一个!🙌
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的两个功能:
container<Key>(keyedBy: Key.Type)
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.
- 一切都如上所述,只不过不使用任何键。
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!
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!!!!!
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,最后返回新创建的结构体对象