Codable Swift — JSON Parsing

How to parse JSON using Coding Keys in iOS

Master the art of JSON parsing in iOS with our in-depth guide on utilizing Coding Keys. Learn efficient techniques to map JSON data to Swift models.

Kanagasabapathy Rajkumar
7 min readNov 20, 2023

JSON parsing in Swift is to decodes JSON to display the data in a user-interactive way. Translation of the data to a readable format.

Let’s explore the JSON parsing with the following protocols

  1. Decodable
  2. Encodable
  3. Codable
  4. Identifiable
  5. Equatable
  6. Hashable
  7. Comparable

We will explore CodingKeys and Container.

This article has been updated and moved to my website here

Older Approach

JSON Serialization

JSON Serialization is an older approach to parsing the JSON.

import Foundation

// Your JSON data as Data
let jsonData = """
{
"name": "Sabapathy Rajkumar",
"age": 33,
"email": "saba@iosdev.com"
}
""".data(using: .utf8)!

do {
// Parse the JSON data
if let json = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
// Access individual values from the JSON dictionary
if let name = json["name"] as? String,
let age = json["age"] as? Int,
let email = json["email"] as? String {
print("Name: \(name)")
print("Age: \(age)")
print("Email: \(email)")
}
}
} catch {
print("Error parsing JSON: \(error.localizedDescription)")
}

Preferred Approach

Codable Protocol is widely used in Swift programming and it helps in parsing the data. Based on the Swift Standard Library

Before going into the Codable, we should understand the benefits of Decodable and Encodable

Decodable

A type that can decode itself from an external representation.

protocol Decodable

Conforming to the Decodable allows for the creation of model objects directly from the external representation such as JSON or GraphQL. It can be via an API request.

Conforming to the Decodable Protocol

struct Person: Decodable {
let name: String
let age: Int
}

Decoding Data

import Foundation

// Your JSON data as Data
let jsonData = """
{
"name": "Sabapathy Rajkumar",
"age": 33,
"email": "saba@iosdev.com"
}
""".data(using: .utf8)!
do {
let person = try JSONDecoder().decode(Person.self, from: jsonData)
print("Name: \(person.name)")
print("Age: \(person.age)")
} catch {
print("Error decoding JSON: \(error.localizedDescription)")
}

Encodable

A type that can encode itself to an external representation.

protocol Encodable

Conforming to the Encodable used for encoding or serializing Swift objects into the external representation such as JSON or GraphQL. It helps with data interchange, persistence or transmission over the network or updates the data in the database via API request.

let person = Person(name: "Sabapathy Rajkumar", age: 33)

do {
let jsonData = try JSONEncoder().encode(person)
let jsonString = String(data: jsonData, encoding: .utf8)
print("JSON: \(jsonString ?? "")")
} catch {
print("Error encoding JSON: \(error.localizedDescription)")
}

Codable

A type that can convert itself into and out of an external representation.

typealias Codable = Decodable & Encodable

We can conform to the Codable Protocol when we create a custom custom class, struct or enum. A powerful feature in Swift for JSON parsing. We can parse JSON or any other external representation such as XML, plist (Property Lists) or GraphQL.

import Foundation

// Define a struct or class that conforms to Codable
struct Person: Codable {
let name: String
let age: Int
let email: String
}

// Your JSON data as Data
let jsonData = """
{
"name": "Sabapathy Rajkumar",
"age": 33,
"email": "saba@iosdev.com"
}
""".data(using: .utf8)!

do {
// Decode the JSON data into a Swift object
let person = try JSONDecoder().decode(Person.self, from: jsonData)
print("Name: \(person.name)")
print("Age: \(person.age)")
print("Email: \(person.email)")
} catch {
print("Error decoding JSON: \(error.localizedDescription)")
}

CodingKeys

Let’s discuss CodingKeys. Why do we need CodingKeys in the Codable protocol?

protocol CodingKey : CustomDebugStringConvertible, CustomStringConvertible, 
Sendable

CodingKeys are a powerful way to customize the encoding and decoding of your Swift types to and from JSON.

Conversion between CamelCase and snake_case

{
"username": "sabapathy",
"user_age": 33,
"user_mail": "saba@iosdev.com",
"occupation": "iOS Developer"
"password": "****"
}

Use-cases:

Mapping JSON keys with swift properties

{
"username": "sabapathy",
"user_age": 33,
}
struct User: Codable {
let username: String
let age: String

enum CodingKeys: String, CodingKey {
case userName
case age = "user_age"
}
}

Ignoring properties

If we want to exclude the properties from encoding and decoding, we can ignore such properties

struct User: Codable {
let username: String
let password: String

enum CodingKeys: String, CodingKey {
case username
// Omit password during encoding and decoding
}
}

Container refers to an object provided by Swift’s Codable framework that allows you to access and decode values from a JSON representation. Containers are used to navigate the hierarchical structure of JSON data and extract values associated with specific keys.

Two primary types of containers are used in JSON parsing with Codable:

Keyed Container

In Encodable

func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> 
where Key : CodingKey

In Decodable

func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> 
where Key : CodingKey

Take the example of a Container

struct User: Decodable {
let username: String
let age: Int

enum CodingKeys: String, CodingKey {
case username
case age = user_age
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
age = try container.decode(Int.self, forKey: .age)
}
}

Unkeyed Container is used when decoding JSON arrays. It allows you to iterate over an array of values without explicit keys. You typically use this container when decoding JSON arrays of objects.

struct User: Decodable {
let usernames: [String]

enum CodingKeys: String, CodingKey {
case usernames
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var usernamesContainer = try container.nestedUnkeyedContainer(forKey: .usernames)
var usernamesArray = [String]()

while !usernamesContainer.isAtEnd {
let username = try usernamesContainer.decode(String.self)
usernamesArray.append(username)
}

usernames = usernamesArray
}
}

Advantages of JSONDecoder over JSONSerialization

  1. JSONDecoder uses the high-level mechanism specially designed for mapping JSON data to custom Swift types
  2. User-friendly because it automatically maps JSON keys to Swift properties.
  3. Automatic error handling and customising the decoding process by implementing the initializer using CodingKeys for renaming or omitting properties
  4. Highly recommended while dealing with complex data structures and APIs.
Photo by Meizhi Lang on Unsplash

Identifiable

A class of types whose instances hold the value of an entity with stable identity.

protocol Identifiable<ID>

Identifiable provides a default implementation for class types using ObjectIdentifier which is unique for the lifetime of the object. It is always unique like UUIDs.

struct Caretaker: Codable, Identifiable {
var id = UUID()
let caretakerPic: String
let caretakerName: String

enum CodingKeys: String, CodingKey {
case caretakerPic = "caretaker-pic"
case caretakerName = "caretaker-name"
}
}

Use Case

To have uniqueness in the models and that helps in iterating easily.

When to use Identifiable protocol

  • In SwiftUI — Iterating the elements of the Collection or Model Objects.
// Model 
struct Chatlog: Codable, Identifiable {
var id = UUID()
let messageID: Int
let text: String
}
//Usage of Identifiable protocol in ForEach
@State var chatlog: [Chatlog]
ForEach(chatlog.sorted { $0.messageID < $1.messageID}, id: \.id) { chat in
ChatView(chatlog: chat)
}
  • Modelling entities with identity using Swift value types
Photo by Belinda Fewings on Unsplash

Equatable

A class of types whose instances hold the value of an entity with stable identity.

Hashable and Comparable conforms to Equatable Protocol

protocol Equatable

Use Case

For a Structure and Enum must conform to Equatable protocol for automatic synthesize.

Swift — Composable Architecture uses Equatable protocol for States and Actions. Please read my recent article on TCA — The Composable Architecture.

When to use the Equatable protocol

  1. Lack of Identity
  2. Attribute-based Equality
  3. Immutability
  4. Automatic Synthesize
  5. Equality by reference
Photo by Shubham Dhage on Unsplash

Hashable

A type that can be hashed into a Hasher to produce an integer hash value.

protocol Hashable : Equatable

Use Case

Hash values allow for efficient data retrieval in collections.

When to use Hashable Protocol

struct TableViewData: Hashable, Identifiable, Decodable {
var id = UUID()
var base: String
var currencyCode: String
var currencyName: String
}

In SwiftUI, we can make use of \.self in ForEach.

ForEach(viewModel.tableViewDataArray, id: \.self) {
Text($0.currencyCode).tag($0.currencyCode)
}

It is said to be KeyPaths. Swift generates hash values for each object and uses them to identify it. CoreData is the ideal example for Hashable because it’s already compliant with the protocol.

Photo by Igor Omilaev on Unsplash

Comparable

A type that can be compared using the relational operators <, <=, >=, and >.

protocol Comparable : Equatable

The Comparable protocol is used for types that have an inherent order, such as numbers and strings.

Use Case

When you have two Struct objects — first name, last name, city. We can compare those two instances by using the Comparable protocol.

struct Person: Comparable {
var firstName: String
var lastName: String
var age: Int
}
let person1 = Person(firstName: "John", lastName: "Doe", age: 30)
let person2 = Person(firstName: "Jane", lastName: "Smith", age: 25)

// Comparable usage
let people = [person1, person2]
let sortedPeople = people.sorted()

print("People sorted by last name and age:")
for person in sortedPeople {
print("\(person.firstName) \(person.lastName), \(person.age)")
}

When to use Comparable

  1. Incomparable Limitations with No Equal
  2. Comparable Benefits
struct Date {
let year: Int
let month: Int
let day: Int
}

extension Date: Comparable {
static func < (lhs: Date, rhs: Date) -> Bool {
if lhs.year != rhs.year {
return lhs.year < rhs.year
} else if lhs.month != rhs.month {
return lhs.month < rhs.month
} else {
return lhs.day < rhs.day
}
}
}

Grateful for your read, now let’s code on!

Interested in connecting? 
Feel free to connect with me on: LinkedIn and GitHub

Please take a look at my first-ever Article 👇

--

--

Kanagasabapathy Rajkumar
Kanagasabapathy Rajkumar

Written by Kanagasabapathy Rajkumar

Swift Enthusiast | Building Seamless iOS Experiences 🚀 | Swift & Objective-C |