In my last post, I went over how to get data from an API. The API returned a JSON object, and I did some pretty janky basic parsing of that data into an object. I’ve been aware of the Codable protocol, but I didn’t want to use it until I had time to dive deep into it. Well, today’s the day I finally learn how to use the Codable protocol with JSONDecoder.

Note: This will build off of the last post I did about infinity lists.

The Simplicity of Codable

Codable, as with most of Swift, is both simple and powerful. If you don’t need to do anything special, it works out of the box. If you’re a special snowflake ❄️ and have a special case, it’s extensible and easy to tweak for more advanced cases. It also works with with things like Optional, Arrays, and Dictionaries. For reference, this article from Apple pretty much explains what all you can do with it.

Let’s start off with a simple case before working on my code. Suppose we have an API that returns the following JSON:

{ status: "OK", username: "SchwiftyUI" }

To parse a simple object like this, we only need a handful of lines of code. All we need to do is to create an object that mirrors the response, the have it conform to the Codable protocol. After that, we can call JSONDecoder().decode() on the data to get the object.

func parseJson(data: Data) -> DeserializedResponse { do { return try JSONDecoder().decode(DeserializedResponse.self, from: data) } catch { fatalError("Can't parse JSON") } } class DeserializedResponse: Codable { var status: String var username: String }

Fixing Updating My Code

Let’s take a look at what my original code for parsing the data looked like.

func getArticlesFromJson(content: Data) -> [NewsListItem] { let jsonObject = try! JSONSerialization.jsonObject(with: content) // Bad result guard let resultMap = jsonObject as? [String: Any] else { return [] } // Result not OK if resultMap["status"] as! String != "ok" { return [] } // Try to get the articles guard let articleMapList = resultMap["articles"] as? [[String: Any]] else { return [] } var newsItems = [NewsListItem]() for articleMap in articleMapList { guard let title = articleMap["title"] as? String else { continue } guard let author = articleMap["author"] as? String else { continue } newsItems.append(NewsListItem(title: title, author: author)) } return newsItems } class NewsListItem: Identifiable { var title: String = "" var author: String = "" var uuid: String = UUID().uuidString init(title: String, author: String) { self.title = title self.author = author } }

My code uses the JSONSerialization, which generally returns a map of the json property names onto their values. This works pretty well, and I think this used to be the gold standard for parsing json strings into real objects.

What I want to do is move to a class that conforms to Codable so I can just use JSONDecoder. To do this, I need to structure my classes to look the same as the JSON that NewsApi.org returns. It returns a JSON with a status and an optional list of articles. Each of these articles has an optional author and a title. Using the Codable protocol, we can very easily create this structure and have swift parse it out:

func getArticlesFromJson(content: Data) -> [NewsListItem] { var response: NewsApiResponse do { response = try JSONDecoder().decode(NewsApiResponse.self, from: content) } catch { return [] } if response.status != "ok" { return [] } return response.articles ?? [] } class NewsApiResponse: Codable { var status: String var articles: [NewsListItem]? } class NewsListItem: Codable, Identifiable { var uuid: UUID = UUID() var title: String var author: String? // Need coding keys since we're also creating a UUID enum CodingKeys: String, CodingKey { case author = "author" case title = "title" } }

As you can see, this code is a lot more readable and understandable. It’s still not perfect, but it’s a lot better than it was before. It also helped me catch a bug where I didn’t realize that not every article has an and author 😁.

The last question I have is what is the performance difference between these two methods. I’m guessing that the Codable protocol is going to be faster, but let’s put this theory to the test. I’ll write a for loop to run these parsers a couple thousand times to see what the difference is.

// Get data form the API print("Starting old parsing using mapping now") var date = NSDate.now for i in 1...10000 { oldGetArticlesFromJson(content: content) } print("Ended, Time taken: \(-1 * date.timeIntervalSinceNow) seconds") print("Starting new parsing using codable now") date = NSDate.now for i in 1...10000 { newGetArticlesFromData(content: content) } print("Ended, Time taken: \(-1 * date.timeIntervalSinceNow) seconds")

Wow, Codable is a lot slower than I expected. Running this a few times and scrolling down shows that Codable is consistently ~25% slower than using JSONSerialization. This isn’t a big issue as this runs in under a millisecond when we really call it, but it’s still good to know. I’m also not the only person that noticed this.

What Now?

I’m really happy with Codable, and I’m glad I took the time to learn how to use it. This is going to me hitting APIs and parsing data from them so much quicker.

As always, here’s a link to my most recent video tutorial using this.

Thanks for reading

– SchwiftyUI