Home Articles

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.

Demo of JSON Parsing

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.

Older Approach

JSON Serialization is an older approach to parsing the JSON.

JSON Serialization

Here's the example


    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.

Identifiable

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

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

  1. Modelling entities with identity using Swift value types

Equatable

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.

When to use the Equatable protocol

  1. Lack of Identity
  2. Attribute-based Equality
  3. Immutability
  4. Automatic Synthesize
  5. Equality by reference

Hashable

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

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

Please find the complete project

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.