Skip to main content
Swift for iOS Development
CHAPTER 19 Beginner

JSON Parsing and Codable

Updated: May 16, 2026
7 min read

# CHAPTER 19

JSON Parsing and Codable

1. Introduction

In the previous chapter, we successfully downloaded data from a URL using URLSession. However, the internet sent us raw binary Data. It looks like 01011010. We cannot display that in a UI. Modern REST APIs format their data using JSON (JavaScript Object Notation). We must mathematically map this text-based JSON into native Swift Structs. Historically, this was a nightmare of manual dictionary casting. Today, Apple provides a magical protocol. In this chapter, we will master JSON Parsing and Codable. We will learn how to mirror JSON structures with Swift Models and utilize JSONDecoder to automatically translate internet data into usable objects.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Read and understand raw JSON data structures.
  • Create Swift Models that perfectly mirror JSON layouts.
  • Conform models to the Codable protocol.
  • Utilize JSONDecoder to translate raw binary Data into Swift objects.
  • Handle nested JSON objects.

3. Understanding JSON

APIs return data in JSON. It consists of Key-Value pairs wrapped in curly braces {} (for objects) or square brackets [] (for arrays).

Imagine an API endpoint https://api.example.com/user returns this JSON:

json
12345
{
  "id": 101,
  "name": "Leanne Graham",
  "email": "leanne@gmail.com"
}

4. The Codable Protocol (The Blueprint)

To parse this in Swift, we must create a struct that matches the JSON *exactly*. The variable names in your struct MUST perfectly match the Keys in the JSON. Most importantly, the struct must conform to the Codable protocol!
swift
123456789
import Foundation

// 1. Matches the JSON structure exactly!
// 2. Conforms to Codable!
struct APIUser: Codable, Identifiable {
    let id: Int
    let name: String
    let email: String
}

5. Using JSONDecoder (The Translator)

Now we integrate our new Model with our networking code from the previous chapter. Once URLSession gives us the raw binary data, we pass it into a JSONDecoder().
swift
1234567891011121314151617181920212223
@MainActor
class UserViewModel: ObservableObject {
    @Published var users: [APIUser] = [] // We will store the parsed data here!
    
    func fetchUsers() async {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
        
        do {
            // 1. Fetch the raw binary Data
            let (data, _) = try await URLSession.shared.data(from: url)
            
            // 2. The Magic Translation!
            // We tell the Decoder: "Try to decode this raw data into an Array of APIUser structs!"
            let decodedData = try JSONDecoder().decode([APIUser].self, from: data)
            
            // 3. Success! Update the UI Array!
            self.users = decodedData
            
        } catch {
            print("Failed to decode JSON: \(error)")
        }
    }
}

6. Handling Mismatched Keys (CodingKeys)

What if the API uses ugly, snake_case JSON keys (e.g., "first_name": "Alice"), but you want your Swift code to use beautiful camelCase (firstName)? If the names don't match exactly, the JSONDecoder will crash.

You must provide a translation dictionary inside your struct called CodingKeys!

json
12345
// The ugly JSON
{
  "user_id": 99,
  "first_name": "Bob"
}
swift
12345678910
struct CustomUser: Codable {
    let id: Int
    let firstName: String
    
    // The Translation Dictionary!
    enum CodingKeys: String, CodingKey {
        case id = "user_id"         // Maps 'id' to 'user_id'
        case firstName = "first_name" // Maps 'firstName' to 'first_name'
    }
}

7. Mini Project: The Full API Pipeline

Let's build a single file that downloads a user, parses it, and displays it on screen.
swift
12345678910111213141516171819202122232425262728293031323334353637383940414243
import SwiftUI

struct NetworkUser: Codable, Identifiable {
    let id: Int
    let name: String
    let username: String
}

@MainActor
class NetworkViewModel: ObservableObject {
    @Published var userList: [NetworkUser] = []
    
    func downloadData() async {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            let parsedUsers = try JSONDecoder().decode([NetworkUser].self, from: data)
            self.userList = parsedUsers
        } catch {
            print("Error: \(error)")
        }
    }
}

struct ApiListView: View {
    @StateObject private var vm = NetworkViewModel()
    
    var body: some View {
        NavigationStack {
            List(vm.userList) { user in
                VStack(alignment: .leading) {
                    Text(user.name).font(.headline)
                    Text("@\(user.username)").foregroundColor(.gray)
                }
            }
            .navigationTitle("Live Users")
            .task {
                // .task is like .onAppear, but specifically built for async functions!
                await vm.downloadData()
            }
        }
    }
}

8. Common Mistakes

  • Type Mismatches: If the JSON says "id": 101 (an Integer), but your Swift struct says let id: String, the JSONDecoder will instantly throw a catastrophic error and fail the entire parsing process. The data types must match the JSON payload with 100% accuracy.
  • Missing Optionality: If an API sometimes returns an "age" field, and sometimes completely omits it, your Swift struct MUST define it as optional: let age: Int?. If it is not optional, and the field is missing in the JSON, the parser crashes.

9. Best Practices

  • Print the Error: If your do-catch block is failing, do not just print("Failed"). ALWAYS print error. The Swift JSONDecoder is brilliant; it will specifically tell you exactly which key failed: *"Expected Int for key 'score' but found String"*.

10. Exercises

  1. 1. Look at this JSON: {"title": "Matrix", "year": 1999}. Write a Swift struct that conforms to Codable to match this layout.
  1. 2. If you want to use the variable let movieTitle: String instead of title, write the CodingKeys enum to map it correctly.

11. Coding Challenges

Challenge: Assume you have successfully downloaded raw Data. Instantiate a JSONDecoder. Call the .decode() method, explicitly passing in your Movie.self struct and the raw data. Store the result in a variable. Wrap this entire operation in a do-catch block.

12. MCQ Quiz with Answers

Question 1

What protocol must a Swift structure explicitly conform to in order to grant the JSONDecoder engine permission to map raw API binary data directly into the struct's properties?

Question 2

A developer creates a struct with let age: Int. The API responds with {"age": "25"}. What will happen during the JSONDecoder().decode execution?

13. Interview Questions

  • Q: Explain the mechanical interaction between a REST API JSON payload, the Codable protocol, and the JSONDecoder class.
  • Q: Describe a scenario where implementing a custom CodingKeys enumeration is architecturally mandatory for a Codable model.
  • Q: Why must API fields that represent unstable or inconsistent data (e.g., a "middle_name" field that only appears for 50% of users) be strictly marked as Optionals (?) within the Swift model?

14. FAQs

Q: What is the difference between Decodable and Codable? A: Codable is actually a combination of two protocols: Decodable (turning JSON into Swift) and Encodable (turning Swift into JSON to upload to a server). If you are only downloading data, you technically only need Decodable!

15. Summary

In Chapter 19, we completed the full network bridge. We analyzed raw text-based JSON payloads from external REST APIs and engineered native Swift Models that mirrored their structure. By adopting the magical Codable protocol, we empowered the JSONDecoder engine to mathematically translate raw binary bytes directly into strongly-typed Arrays of Swift Objects. We conquered structural discrepancies utilizing translation CodingKeys, ensuring our modern UI architectures can flawlessly render dynamic global data.

16. Next Chapter Recommendation

Our app can now download and display the latest news from the internet. But what about saving a simple setting, like "Dark Mode enabled", locally on the user's physical phone? Proceed to Chapter 20: Local Storage with UserDefaults.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·