Home Articles

How to parse JSON using Coding Keys in iOS

JSON Parsing - Codable Protocol

JSON Parsing - Codable Protocol

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.

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)")
}

JSON Serialization Example

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

Decodable Protocol

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
}

Person Model

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)")
}

Decoding data using JSONDecoder

Encodable

A type that can encode itself to an external representation.

protocol Encodable

Encodable Protocol

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)")
}

Encoding Data using JSONEncoder

Codable

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

typealias Codable = Decodable & Encodable

Codable Protocol

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)")
}

Conforms to Codable Protocol and Decoding the data

CodingKeys

Let’s discuss CodingKeys

Why do we need CodingKeys in the Codable protocol?

protocol CodingKey : CustomDebugStringConvertible, CustomStringConvertible, 
                      Sendable

CodingKey Protocol

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": "****"
}

Example JSON

Use-cases

Mapping JSON keys with swift properties

{
    "username": "sabapathy",
    "user_age": 33,
}

Example JSON for this usecase

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"
    }
}

CodingKeys Example

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
    }
}

Ignoring property - Password

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

Encodable - Keyed Container

In Decodable

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

Decodable - Keyed Container

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)
    }
}

Keyed Container Example

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
    }
}

Unkeyed Container Example

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

Identifiable real-time example

Identifiable real-time example

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

protocol Identifiable<ID>

Identifiable Protocol

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.

Practical Example

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"
    }
}

Identifiable Example

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
}

Chatlog Model

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)
}

Usage of Identifiable

  • Modelling entities with identity using Swift value types

Equatable

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

Equatable real-time example

Equatable real-time example

🤎

Hashable and Comparable conforms to Equatable Protocol

Definition

protocol Equatable

Equatable Protocol

Use Case

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

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.

Hashable real-time example

Hashable real-time example

Definition

protocol Hashable : Equatable

Hashable Protocol

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
}

Hashable Use case

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

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

SwiftUI Example

It is said to be KeyPathsSwift 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 >.

Comparable real-time example

Comparable real-time example

Definition

protocol Comparable : Equatable

Comparable Protocol

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)")
}

Comparable Usage with two different models

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
        }
    }
}

Comparable Benefits

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.