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.
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
- Decodable
- Encodable
- Codable
- Identifiable
- Equatable
- Hashable
- 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
- JSONDecoder uses the high-level mechanism specially designed for mapping JSON data to custom Swift types
- User-friendly because it automatically maps JSON keys to Swift properties.
- Automatic error handling and customising the decoding process by implementing the initializer using CodingKeys for renaming or omitting properties
- Highly recommended while dealing with complex data structures and APIs.
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
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
- Lack of Identity
- Attribute-based Equality
- Immutability
- Automatic Synthesize
- Equality by reference
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.
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
- Incomparable Limitations with No Equal
- 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
}
}
}