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.
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)")
}
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
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)")
}
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.
Encoding Data
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)")
}
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.
Decoding Data
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)")
}
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": "****"
}
Mapping JSON keys with swift properties
{
"username": "sabapathy",
"user_age": 33,
}
Lets take an example - User Model conforms to Codable
protocol
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
It 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
}
}
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.
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"
}
}
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
//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
A class of types whose instances hold the value of an entity with stable identity.
🤎
Hashable and Comparable conforms to Equatable Protocol
protocol Equatable
For a Structure and Enum must conform to Equatable
protocol for automatic synthesize.
Lack of Identity
Attribute-based Equality
Immutability
Automatic Synthesize
Equality by reference
A type that can be hashed into a Hasher
to produce an integer hash value.
protocol Hashable : Equatable
Hash values allow for efficient data retrieval in collections.
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.
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.
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)")
}
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
}
}
}
Note
Please find the GitHub Repo
This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.