Swift
JSON
File Handling
Programming
iOS Development

Reading in a JSON File Using Swift

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Reading a JSON file in Swift is usually a two-step task: load the file's bytes as Data, then decode that data into either a strongly typed model or a dynamic dictionary-like structure. For most app code, Codable plus JSONDecoder is the right default because it gives you compile-time structure and better errors.

The important choice is not "can Swift parse JSON." It is whether the JSON schema is known ahead of time. If it is, use models. If it is highly dynamic, fall back to JSONSerialization.

Load the File Data First

If the JSON file is bundled with the app, start by locating it in the bundle and reading it into memory:

swift
1import Foundation
2
3enum JSONLoadError: Error {
4    case fileNotFound
5}
6
7func loadJSONData(named name: String) throws -> Data {
8    guard let url = Bundle.main.url(forResource: name, withExtension: "json") else {
9        throw JSONLoadError.fileNotFound
10    }
11
12    return try Data(contentsOf: url)
13}

This works well for sample content, bundled configuration, and test fixtures that ship inside the app target.

If the file lives elsewhere, such as the documents directory, the decode step stays the same and only the URL changes.

Decode Into a Codable Model

For known JSON structure, define a type that matches the file and decode directly:

swift
1import Foundation
2
3struct User: Codable {
4    let name: String
5    let age: Int
6    let skills: [String]
7}
8
9func loadUser() throws -> User {
10    let data = try loadJSONData(named: "user")
11    return try JSONDecoder().decode(User.self, from: data)
12}
13
14do {
15    let user = try loadUser()
16    print(user.name)
17} catch {
18    print("Failed to decode user:", error)
19}

This is the safest and most maintainable approach because the compiler checks the expected shape. If the JSON changes, decoding errors usually point straight to the mismatch.

Decode Arrays and Nested Objects

Codable scales well when the file contains arrays or nested models:

swift
1import Foundation
2
3struct Project: Codable {
4    let title: String
5    let owner: User
6}
7
8struct User: Codable {
9    let name: String
10    let age: Int
11}
12
13func loadProjects() throws -> [Project] {
14    let data = try loadJSONData(named: "projects")
15    return try JSONDecoder().decode([Project].self, from: data)
16}

Once your model structure mirrors the JSON structure, Swift handles the traversal for you.

Map JSON Names to Swift Names

JSON keys do not always match Swift naming style. Use CodingKeys when the wire format uses names such as full_name but your Swift model should use fullName.

swift
1import Foundation
2
3struct User: Codable {
4    let fullName: String
5    let age: Int
6
7    enum CodingKeys: String, CodingKey {
8        case fullName = "full_name"
9        case age
10    }
11}

That keeps your Swift code idiomatic without forcing you to rename keys in the source file.

Use JSONSerialization for Dynamic Payloads

If the schema is not known in advance, JSONSerialization can parse the file into dictionaries and arrays:

swift
1import Foundation
2
3let data = try loadJSONData(named: "config")
4let object = try JSONSerialization.jsonObject(with: data, options: [])
5
6if let dictionary = object as? [String: Any] {
7    print(dictionary["environment"] ?? "missing")
8}

This is flexible, but it is also less safe. You lose type checking and have to cast values manually. Use it only when the payload is truly dynamic.

Handle Errors Usefully

Do not collapse every failure into a generic message. Swift decoding errors are often specific enough to tell you what went wrong.

swift
1do {
2    let user = try JSONDecoder().decode(User.self, from: data)
3    print(user)
4} catch DecodingError.keyNotFound(let key, _) {
5    print("Missing key:", key.stringValue)
6} catch DecodingError.typeMismatch(let type, _) {
7    print("Type mismatch for:", type)
8} catch {
9    print("Other decoding error:", error)
10}

That makes bad fixtures and schema mismatches much easier to diagnose than a flat "JSON parsing failed."

Common Pitfalls

The most common mistake is forgetting to include the file in the app target. The JSON file may appear in Xcode, but if it is not part of the bundle, Bundle.main.url will still return nil.

Another common issue is reaching for JSONSerialization by default when the structure is already known. That throws away type safety and makes later code harder to maintain.

Developers also often ignore decoding errors and then debug the wrong layer. If a model field is named incorrectly or typed incorrectly, the decoder is usually already telling you exactly what failed.

Finally, do not assume a bundled JSON file and a documents-directory JSON file should be loaded the same way. The decoding logic is the same, but the file lookup path is not.

Summary

  • Reading JSON in Swift means loading Data first and decoding second.
  • Use Bundle.main.url for JSON files that ship with the app.
  • Prefer Codable and JSONDecoder when the JSON structure is known.
  • Use CodingKeys when JSON names and Swift property names differ.
  • Reserve JSONSerialization for genuinely dynamic payloads.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.