Most apps we build function based on a flow of data, whether it be data that lives on the device or data consumed via an API, there really is no escaping it. One of the many tools we have as Swift developers is Codable
, a protocol designed to help us parse data to and from custom types.
Codable
is a typealias
around the Encodable
and Decodable
protocols, if you don't need bidirectional encoding & decoding you can conform to either of these on their own. So if you just need to encode data and don't care about decoding, your model only needs to conform to Encodable
.
Let's look at how we might use Codable
by taking look at an example playground.
import Foundation
// 1
let userProfile: [String: Any] = [
"username": "swiftCompiled",
"avatar": "https://bit.ly/2A1qSLW",
"last_seen": "01/01/2020",
"is_active": true
]
// 2
struct UserProfile: Codable {
let username: String
let avatar: URL
let lastSeen: String
let isActive: Bool
}
// 3
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
var profile: UserProfile?
do {
// 4
let data = try JSONSerialization.data(withJSONObject: userProfile, options: .prettyPrinted)
profile = try jsonDecoder.decode(UserProfile.self, from: data)
print(profile)
} catch {
print(error)
}
// 1 The JSON object we want to encode.
// 2 Here we define our model, note that the naming convention of the variables is the equivalent camel case naming of that in our userProfile
object. For example, last_seen
in our JSON object maps to the lastSeen
variable in our struct, we can define our mapping for the keys manually if we need to, more on that later.
// 3 Next we create our JSONDecoder
object and set the keyDecodingStrategy
to convertFromSnakeCase
. As discussed above, this tells the decoder than any snake case key in the JSON should be converted to camel case, last_seen
to lastSeen
for example.
// 4 Here's where the magic happens; we turn our userProfile
JSON into an instance of Data
using JSONSerialization
and pass the resulting Data
into jsonDecoder.decode
, this gives us our UserProfile
.
This is a great first step, and in many cases is all we need, but it's only the first half of the equation, what about encoding? This couldn't be simpler, expanding on our previous example let's turn our UserProfile
back into JSON.
// 1
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
do {
// 2
let jsonData = try encoder.encode(profile!)
// Do something with the data
} catch {
print(error)
}
// 1 Create a JSONEncoder
object and set our key decoding type, in our case this directly mirrors that of the JSONDecoder
, this would be fairly common when sending/receiving data from a server.
// 2 Convert our profile
object to data using JSONEncoder.encode
, this takes a single parameter - any object conforming to Encodable
- or Codable
in our case - as that encapsulates both Encodable
and Decodable
as we discovered earlier.
Lastly, let's consider a scenario where we want to encode some JSON into an object but need to use different keys. We can achieve this using a nested enum
called CodingKeys
, whose values are of type of type String
and conforms to the CodingKey
protocol. The name of each case should reflect the variable name in the struct exactly, and the value should be the key in the JSON we're converting. Let's see an example, make the following changes to the keys in the JSON object.
let userProfile: [String: Any] = [
"name": "swiftCompiled",
"profile_pic": "https://swiftcompiled.com/content/images/size/w1000/2020/01/xsc-logo-main-1.png.pagespeed.ic.8eAetH0emD.png",
"last_seen_at": "01/01/2020",
"is_active": true
]
Then directly below let isActive: Bool
in our struct, add the following.
enum CodingKeys: String, CodingKey {
case username = "name"
case avatar = "profile_pic"
case lastSeen = "last_seen_at"
case isActive = "is_active"
}
Now the above JSON will map perfectly to our struct. We don't need to do anything extra with CodingKeys
, they're automatically picked up during the encoding/decoding process.
This brief introduction to how Codable
works in Swift should get you up and running in most cases, if you need to know more you can find a more elaborate guide in the docs.
Thanks for reading ✌