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 ✌