Now, think about the structure of the JSON above. First of all, the whole thing is a JSON object.
This JSON object contains a JSON array named "contacts."
The "contacts" array contains a list of two JSON objects.
Each JSON object contains a key-value pair, where the key is "name."
Now, think about a similar struct/class in Swift. Can we represent the above JSON structure with a Swift struct?
Yes, we do. What about the following struct?
The first struct ContactName represents the objects in the "contacts" array, and the second struct ContactNames represents the "contacts" array.
Parsing JSON
Let's write the code to parse the JSON response we receive after calling getall. Let's create a new Xcode project, App11, and integrate Alamofire into the project using CocoaPods.
Set up the same Views, Data Models and APIConfigs (MainScreenView.swift, ContactsTableViewCell.swift, APIConfigs.swift, and Contact.swift) as App10.
Do not forget to change the URL in APIConfigs.swift file to json:
The project file structure would look like this:
Let's open the View Controller and write the initial controller code similar to App10.
Representing the 'getall' JSON with Swift (ContactNames.swift)
Now, let's create the ContactNames.swift file to represent the JSON structure:
Creating the 'getall' request using Alamofire
Open ViewController.swift file, and let's write the following code in getAllContacts():
In the above code, we are
Setting the method to GET (line 4).
Now, we are not receiving a regular String like before; we are receiving a JSON response. So, we will use responseData instead of responseString (line 4).
Like before, if there is no network error and the status code is a 200-level code (line 15), we will decode the data using JSONDecoder().
In lines 20-22, we decode the received data using the decoder with the data model we wrote, ContactNames. Notice that we are using a try-catch block. Since the decoder might have a runtime exception, we have to handle it gracefully in the catch block.
The receivedData is an object of the ContactNames struct, which corresponds to the JSON object we received. In the object, we have a contacts array. (Review ContactNames.swift).
So, receivedData.contacts (line 24), should contain the data from JSON's contacts array.
In line 24, we are running through each ContactName object and appending the name from the object to contactNames array for the table view (the table view data source).
Then we reload the data for the table view.
Now, this code will not work yet. You will get an error like this:
Adopting Codable protocol
It means the data type (ContactNames) we are using as the blueprint to decode the JSON data, cannot be used until it is a decodable data type. So, we need to adopt a protocol named Codable from the structs ContactName and ContactNames. Let's open ContactNames.swift file and update them by adopting the Codable protocol.
Now, the error will go away.
(Make sure you enable unencrypted HTTP) Let's run the app. It will show us the current contacts.
struct ContactName{
let name:String
}
struct ContactNames{
let contacts: [ContactName]
}
//
// APIConfigs.swift
// App11
//
// Created by Sakib Miazi on 5/26/23.
//
import Foundation
class APIConfigs{
//MARK: API base URL...
static let baseURL = "https://apis.sakibnm.work:8888/contacts/json/"
}
//
// ViewController.swift
// App11
//
// Created by Sakib Miazi on 5/26/23.
//
import UIKit
import Alamofire
class ViewController: UIViewController {
let mainScreen = MainScreenView()
//MARK: list to display the contact names in the TableView...
var contactNames = [String]()
override func loadView() {
view = mainScreen
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Contacts JSON API"
//MARK: setting the delegate and data source...
mainScreen.tableViewContacts.dataSource = self
mainScreen.tableViewContacts.delegate = self
//MARK: removing the separator line...
mainScreen.tableViewContacts.separatorStyle = .none
//get all contact names when the main screen loads...
getAllContacts()
//MARK: add action to Add Contact button...
mainScreen.buttonAdd.addTarget(self, action: #selector(onButtonAddTapped), for: .touchUpInside)
}
@objc func onButtonAddTapped(){
}
func clearAddViewFields(){
mainScreen.textFieldAddName.text = ""
mainScreen.textFieldAddEmail.text = ""
mainScreen.textFieldAddPhone.text = ""
}
func showDetailsInAlert(data: String){
let parts = data.components(separatedBy: ",")
print(parts)
//MARK: trim the whitespaces from the strings, and show alert...
let name = parts[0].trimmingCharacters(in: .whitespacesAndNewlines)
let email = parts[1].trimmingCharacters(in: .whitespacesAndNewlines)
if let phone = Int(parts[2].trimmingCharacters(in: .whitespacesAndNewlines)){
//MARK: show alert...
let message = """
name: \(name)
email: \(email)
phone: \(phone)
"""
let alert = UIAlertController(title: "Selected Contact", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
}
}
//MARK: add a new contact call: add endpoint...
func addANewContact(){
}
//MARK: get all contacts call: getall endpoint...
func getAllContacts(){
}
//MARK: get details of a contact...
func getContactDetails(name: String){
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contactNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "names", for: indexPath) as! ContactsTableViewCell
cell.labelName.text = contactNames[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
getContactDetails(name: self.contactNames[indexPath.row])
}
}
//
// ContactNames.swift
// App11
//
// Created by Sakib Miazi on 5/26/23.
//
import Foundation
struct ContactName{
let name:String
}
struct ContactNames{
let contacts: [ContactName]
}
//MARK: get all contacts call: getall endpoint...
func getAllContacts(){
if let url = URL(string: APIConfigs.baseURL + "getall"){
AF.request(url, method: .get).responseData(completionHandler: { response in
//MARK: retrieving the status code...
let status = response.response?.statusCode
switch response.result{
case .success(let data):
//MARK: there was no network error...
//MARK: status code is Optional, so unwrapping it...
if let uwStatusCode = status{
switch uwStatusCode{
case 200...299:
//MARK: the request was valid 200-level...
self.contactNames.removeAll()
let decoder = JSONDecoder()
do{
let receivedData =
try decoder
.decode(ContactNames.self, from: data)
for item in receivedData.contacts{
self.contactNames.append(item.name)
}
self.mainScreen.tableViewContacts.reloadData()
}catch{
print("JSON couldn't be decoded.")
}
break
case 400...499:
//MARK: the request was not valid 400-level...
print(data)
break
default:
//MARK: probably a 500-level error...
print(data)
break
}
}
break
case .failure(let error):
//MARK: there was a network error...
print(error)
break
}
})
}
}
//
// ContactNames.swift
// App11
//
// Created by Sakib Miazi on 5/26/23.
//
import Foundation
struct ContactName: Codable{
let name:String
}
struct ContactNames: Codable{
let contacts: [ContactName]
}