Guest User

Untitled

a guest
Dec 14th, 2018
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.35 KB | None | 0 0
  1. # Objective 1 - implement encode(to:) and init(from:) methods to customize encoding and decoding
  2.  
  3. Up to this point you have likely used Codable by simply conforming to the protocol and letting magically it do the work for you. While there is nothing wrong with this, let's look at why you may want to implement Codable's methods yourself.
  4.  
  5. When working with nested JSON and letting Codable do most of the work for us, we have to format our model objects in a specific way, like having multiple nested structs or classes inside of each other:
  6.  
  7. ``` Swift
  8. struct Building: Codable {
  9.  
  10. struct Floor: Codable {
  11.  
  12. struct Room: Codable {
  13. var numberOfChairs: Int
  14. }
  15.  
  16. var rooms: [Room]
  17. }
  18.  
  19. var floors: [Floor]
  20. }
  21. ```
  22.  
  23. While there is nothing wrong with doing this, it isn't commonly done besides when using Codable. When implementing Codable ourselves, have the ability to flatten out the information into a single model object. We'll also look at another reason to implement it ourselves in the next objective as well.
  24.  
  25. Codable is actually a combination of the Encodable and Decodable protocols. Encodable turns your model object's values into a different format such as JSON or a Plist, while Decodable does the opposite. Each protocol has just a single method. These methods will get called when you call `encode` or `decode`. Before we look at how to implement these methods ourselves, you will need to understand a few data types.
  26.  
  27. ### Codable Data Types
  28.  
  29. Looking at both Encodable's `encode(to encoder: Encoder) throws` and Decodable's `init(from decoder: Decoder) throws`, we see that they have an `Encoder` and a `Decoder` argument respectively. They are both protocols, which are then adopted by `JSONEncoder/Decoder` and `PropertyListEncoder/Decoder`, and any custom encoder and decoder that you may create. Both encoders and decoders have essentially the same properties and methods. We'll look at the `Decoder`'s properties and methods below:
  30.  
  31. Properties:
  32. - `codingPath`: Allows you to see the current path in the decoding. It isn't used in the implementations of `Codable`'s methods, but will show up in any errors thrown by the methods.
  33. - `userInfo`: Allows you to pass some information into this dictionary to use when decoding data. This is not often used but can be useful for more advanced implementations.
  34.  
  35. Methods:
  36.  
  37. Each of these methods will return a different kind of "container". A container represents a level or node of the JSON, property list, etc.
  38.  
  39. - `func container<Key>(keyedBy: CodingKey.Protocol) -> KeyedDecodingContainer<Key>`: A keyed container essentially represents a group of key-value pairs. This You can think of the container holding values in the same way as a **dictionary**. You'll also notice that it has an argument that conforms to the `CodingKey` protocol. That means if you are going to use a keyed container, you **must** make an enum for your coding keys.
  40. - `func singleValueContainer() -> SingleValueDecodingContainer`: A single value container simply represents a **single value**, such as a string, number, etc.
  41. - `func unkeyedContainer() -> UnkeyedDecodingContainer`: An unkeyed container contains a group of values without keys, so this closely mirrors an **array**.
  42.  
  43. Using these containers, we are able to navigate through the data and decode it in the same way that it is formatted. Or if we are using `Encoder`'s version of these containers, we can encode our model objects in any format we choose.
  44.  
  45. ### Decoding Example
  46.  
  47. Let's now look at some simple JSON and how to decode it ourselves. Let's say we have this JSON:
  48.  
  49. ``` JSON
  50. {
  51. "name": "John Johnson",
  52. "age": 30
  53. }
  54. ```
  55.  
  56. Our model object would look something like this to begin with:
  57.  
  58. ``` Swift
  59. struct Person: Codable {
  60. var name: String
  61. var age: Int
  62.  
  63. init(name: String, age: Int) {
  64. self.name = name
  65. self.age = age
  66. }
  67. }
  68. ```
  69.  
  70. Before we add the `init(from decoder` initializer, we need to look at the JSON and think about which values we want from it. For every value we want, we need to add its key to a coding keys enum. In this case, we'll want the `name` and `age`:
  71.  
  72. ``` Swift
  73. enum CodingKeys: String, CodingKey {
  74. case name
  75. case age
  76. }
  77. ```
  78.  
  79. As a refresher, there is nothing special about the enum's name `CodingKeys`. We could call it `PersonCodingKeys` or whatever else we wanted to.
  80.  
  81. Now that we have the coding keys, we can implement `init(from decoder)`:
  82.  
  83. ``` Swift
  84. init(from decoder: Decoder) throws {
  85.  
  86. // 1
  87. let container = try decoder.container(keyedBy: CodingKeys.self)
  88.  
  89. // 2
  90. let name = try container.decode(String.self, forKey: .name)
  91. let age = try container.decode(Int.self, forKey: .age)
  92.  
  93. // 3
  94. self.name = name
  95. self.age = age
  96. }
  97. ```
  98.  
  99. - 1: We create a container from the decoder argument.
  100. - 2: We then call the container's `decode` method. When using a keyed container, it has `type` and `key` arguments. There are many different variations of this method that allow you to choose the type the value should be decoded as, then you choose the key from the coding keys you passed into the `container(keyedBy:)` method from step 1. This method throws, so remember to call `try`. There is no need to set up a do-try-catch block since the initializer itself that we are in throws. If we think of the keyed container as a dictionary, then `decode` is the way that we get values out of it with a key.
  101. - 3: We now have the name and age decoded, so we can finish the initializer by setting the instance variable's values. Alternatively, you could call the `Person`'s `init(name: String, age: Int)` to accomplish the same thing.
  102.  
  103. Let's add another level to the JSON:
  104.  
  105. ``` JSON
  106. {
  107. "name": "John",
  108. "age": 30,
  109. "favoriteNumbers": [
  110. 42,
  111. 10,
  112. 13
  113. ]
  114. }
  115. ```
  116.  
  117. Make sure to add a new case to the CodingKeys enum for the "favoriteNumbers" key-value pair. Once that is done, we can add to the the init(from decoder:) from above:
  118.  
  119. ``` Swift
  120. // 1
  121. var favoriteNumbersContainer = try container.nestedUnkeyedContainer(forKey: .favoriteNumbers)
  122.  
  123. // 2
  124. var favoriteNumbers: [Int] = []
  125.  
  126. while !favoriteNumbersContainer.isAtEnd {
  127. let number = try favoriteNumbersContainer.decode(Int.self)
  128. favoriteNumbers.append(number)
  129. ```
  130.  
  131. - 1: Again, for each new "level" of JSON, we need to create a new container of some kind. This new level of JSON is an array, so we'll make an **unkeyed** container. A couple things to note here are that the container must be a variable, which we'll come back to in step 2. The other thing is that we call the `nestedUnkeyedContainer` method on the container we made previously. This is important to remember. We don't always get containers from the decoder, just the top level of the data. From there, you are able to get "nested" containers from other containers. This allows you to drill down into each level of the data.
  132. - 2: There are a few ways that you can decode the data from an unkeyed container. The important thing to remember is that every time you call `decode` in an unkeyed container, that value also gets removed from it. In the example above we are using the container's `isAtEnd` property to see if the container is empty or not. Until it is empty, the while loop will keep decoding the `Int`s in it.
  133.  
  134. At this point, that is about everything you need to know about implementing `Decodable` yourself. Obviously you will see more complicated, nested JSON than this. In that case, you will just make a new container of the correct kind for each level until you are able to access and decode the information that you need for your model object.
Add Comment
Please, Sign In to add comment